diff --git a/README.md b/README.md index 7b0922f..c1f3854 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,97 @@ ms-hub create my-org/my-model --repo-type model --visibility private api.create_repo("my-org/my-model", "model", visibility="private", license="apache-2.0") ``` +### Visibility and gated access + +The SDK keeps its historical `Visibility` integer enum: `PRIVATE=1`, +`INTERNAL=3`, `PUBLIC=5`. Application gating is a separate boolean switch, +`gated_mode=True` / `False`, and is not deprecated. It is effective for private +repositories; with explicit public/internal visibility it is warned about and +ignored. Either explicit boolean with no visibility still implies private. + +Use the same high-level spelling as v0.5.1. A gated repository requires an +approved application to download; the boolean switch does not select an +approval policy: + +```python +api.create_repo( + "my-org/my-data", "dataset", visibility="private", + gated_mode=True, +) + +# Turn the application switch off; the repository becomes ordinary private. +api.update_repo_settings("my-org/my-data", "dataset", gated_mode=False) +``` + +Ordinary creation prefers OpenAPI. Explicit `gated_mode=True/False` creation +retains the legacy `Visibility` plus `ProtectedMode=1/2` request format to keep +v0.5.1 behaviour. Settings adapt the boolean to the OpenAPI access mode internally; +`True` enables gating with the server's `auto` approval default, `False` leaves an +ordinary private repository, and `None` leaves the control unchanged. + +High-level `visibility` accepts the historical enum/labels, not `"gated"`. +`approval_mode` and `gated` request parameters have been removed, including the +`--approval-mode` CLI flag; passing them through `**kwargs` raises `TypeError` +before any request. This does not remove the existing `RepoInfo.gated` boolean +response field or the native `gated_mode` string in new detail responses. +`get_repo()` preserves `Visibility` for models/datasets, while raw `get_model()` / +`get_dataset()` retain native OpenAPI fields. Native approval-policy strings +belong only to the low-level `api.openapi` payloads, not to the high-level +`gated_mode` boolean. + +```bash +ms-hub create my-org/my-data --repo-type dataset --visibility private --gated +``` + +`visibility="internal"` (visible within an organisation) has no equivalent in +the OpenAPI surface, so those requests continue to use the legacy endpoint. + +### Model and dataset zones + +Models belong to the `base`, `aigc`, or `science` zone; datasets to `base` or +`science`. A new repository is always `base`. Upload the files first, then set +the zone — the AIGC endpoint checks that each weight path already exists: + +```python +api.create_repo("my-org/my-lora", "model", visibility="private") +api.upload_folder("my-org/my-lora", "model", "./my-lora-files") + +options = api.list_aigc_model_options() # enum ids, no credentials needed +api.set_model_category( + "my-org/my-lora", + model_category="aigc", + aigc_type="LoRA", + base_model_architecture_type=options["base_architectures"][0]["id"], + base_model_architecture_subtype=options["base_architectures"][0]["subtypes"][0]["id"], + version_name="v1.0", + weight_files=[{"path_in_repo": "model.safetensors"}], + sample_images=[{"url": "https://example.com/sample.png", "prompt": "a cat"}], +) + +# Detail reads can lag behind writes; wait briefly before resolving a new version name. +import time + +for _ in range(10): + detail = api.get_model("my-org/my-lora") + if any(v.get("version_name") == "v1.0" for v in detail.get("versions", [])): + break + time.sleep(1) +else: + raise TimeoutError("The new version is not visible yet; retry the detail read later.") + +# Edit a version's metadata in place, addressed by version name or numeric id. +api.update_model_category("my-org/my-lora", version_name="v1.0", trigger_words=["anime"]) + +# Read the zone fields back; which keys are present depends on the category. +detail = api.get_model("my-org/my-lora") +print(detail["model_category"], [v["version_id"] for v in detail.get("versions", [])]) +``` + +Enum values (`aigc_type`, architectures, subject areas, `task`, `license`) come +from the options endpoints; pass each option's `id`, not its `label`. An +existing version's weights are immutable — changing them means creating a new +version with `set_model_category`. + ### Deploy a Studio ```bash @@ -484,9 +575,9 @@ ms-hub list --repo-type studio --owner my-org ms-hub delete my-org/my-model --repo-type model --yes ``` -> **Deprecation notice:** `delete_repo` / `ms-hub delete` emits a `DeprecationWarning` — programmatic repo deletion is restricted for security reasons and will be restored once token-scoped auth is available. Use the [web console](https://modelscope.cn) to delete repos. +> **Deletion permission:** `delete_repo` / `ms-hub delete` needs repository deletion permission; ordinary write permission alone is not sufficient. An admin-level token has been verified to work. Tokens without the required permission are refused (`10010101003` for models, `10020301011` for datasets) and directed to the [web console](https://modelscope.cn). Retrying a permission rejection with the same token will not help. > -> `delete_files` requires cookie-based session auth; API tokens may receive a 401 error. +> `delete_files` uses the legacy commit/session path. File-deletion permission is distinct from repository-deletion permission; a write token may delete files even when it cannot delete the repository.
ms-hub create options @@ -500,6 +591,7 @@ ms-hub delete my-org/my-model --repo-type model --yes | `--chinese-name` | no | Display name in Chinese | | `--description` | no | Repository description | | `--exist-ok` | no | No error if repository already exists | +| `--gated` / `--no-gated` | no | Historical application switch; explicit public/internal visibility takes precedence | | `--sdk-type` | no | Studio SDK: `gradio`, `streamlit`, `docker`, `static` | | `--sdk-version` | no | Studio SDK version | | `--base-image` | no | Studio base Docker image | @@ -831,9 +923,19 @@ api = HubApi(token="...", endpoint="https://modelscope.ai") | | `whoami()` | Get current user info | | **Repo** | `create_repo(repo_id, repo_type, ...)` | Create a repository | | | `get_repo(repo_id, repo_type)` | Get repository metadata | +| | `get_model(repo_id)` / `model_info(repo_id)` | Model detail, including zone fields | +| | `get_dataset(repo_id)` / `dataset_info(repo_id)` | Dataset detail, including science fields | | | `list_repos(repo_type, ...)` | Paginated listing (`model`, `dataset`, `studio`, `skill`, `mcp`) | -| | `delete_repo(repo_id, repo_type)` | Delete a repository *(deprecated — see note below)* | +| | `delete_repo(repo_id, repo_type)` | Delete a repository *(needs deletion permission — see note below)* | | | `repo_exists(repo_id, repo_type)` | Check existence | +| **Zones** | `set_model_category(repo_id, model_category=...)` | Move a model into `base` / `aigc` / `science` | +| | `update_model_category(repo_id, ...)` | Edit a model's zone fields in place | +| | `set_dataset_category(repo_id, dataset_category=...)` | Move a dataset into `base` / `science` | +| | `update_dataset_category(repo_id, ...)` | Edit a dataset's science fields in place | +| | `list_aigc_model_options()` | AIGC enums (types, architectures, content categories) | +| | `list_science_model_options()` | Science model subject areas | +| | `list_science_dataset_options()` | Science dataset subject areas | +| | `list_model_options()` | Valid model `task` and `license` values | | **Files** | `upload_file(repo_id, repo_type, local, remote)` | Upload a single file | | | `upload_folder(repo_id, repo_type, folder, ...)` | Upload a directory | | | `download_file(repo_id, repo_type, file, ...)` | Download a single file (with retry, resume, offline mode) | @@ -845,7 +947,7 @@ api = HubApi(token="...", endpoint="https://modelscope.ai") | **Deploy** | `deploy_repo(repo_id, repo_type)` | Deploy Studio or MCP | | | `stop_repo(repo_id, repo_type)` | Stop deployment | | | `get_repo_logs(repo_id, ...)` | Fetch logs | -| | `update_repo_settings(repo_id, repo_type, ...)` | Update settings | +| | `update_repo_settings(repo_id, repo_type, ...)` | Update settings (`model`, `dataset`, `studio`, `skill`) | | **Secrets** | `add_secret(repo_id, key, value)` | Add a secret | | | `list_secrets(repo_id)` | List secret keys (values are never returned) | | | `update_secret(repo_id, key, value)` | Update a secret | @@ -872,8 +974,8 @@ api = HubApi(token="...", endpoint="https://modelscope.ai") | | `clear_cache(cache_dir, ...)` | Free disk space | > **Deletion restrictions:** -> - `delete_repo` is deprecated for security reasons (emits `DeprecationWarning`). Will be restored with token-scoped auth. Use the [web console](https://modelscope.cn) instead. -> - `delete_files` requires cookie-based session auth; API tokens (`ms-...`) may receive a 401 error. +> - `delete_repo` needs repository deletion permission. Insufficient permission is rejected (`10010101003` / `10020301011`); use an authorised token or the [web console](https://modelscope.cn). An admin-level token has been verified to work. The SDK uses the legacy endpoint; the current OpenAPI contract has no repository-deletion route. +> - `delete_files` uses the legacy commit/session path and checks file-deletion permission separately. It does not inherit the OpenAPI file-deletion endpoint's 100-path limit. Editing a tag revision may be rejected by repository policy. > **Token permission levels:** every write method needs a token issued with > `write` permission or higher. A rejected write raises `PermissionDeniedError` @@ -942,6 +1044,7 @@ Token is persisted locally after `ms-hub login` and auto-loaded in subsequent se | `MODELSCOPE_API_TIMEOUT` | `60` | HTTP request timeout (seconds) | | `MODELSCOPE_API_CONNECT_TIMEOUT` | `10` | HTTP connect timeout (seconds) | | `MODELSCOPE_API_MAX_RETRIES` | `5` | Regular API retries; downloads and file listings use separate retry policies | +| `MODELSCOPE_OPTIONS_CACHE_TTL` | `300` | Seconds to cache the model/dataset enum option endpoints; `0` disables caching | | `MODELSCOPE_REPO_TREE_PAGE_TIMEOUT_SECONDS` | `15` | Per-page listing timeout (seconds) | | `MODELSCOPE_REPO_TREE_PAGE_MAX_ATTEMPTS` | `4` | Maximum attempts per listing page, including the initial request | | `MODELSCOPE_REPO_TREE_PAGE_RETRY_MAX_DELAY_SECONDS` | `8` | Maximum listing retry delay (seconds) | @@ -1014,6 +1117,35 @@ from modelscope_hub.compat import LegacyHubApi as HubApi All legacy parameter names (`allow_file_pattern`, `ignore_file_pattern`, `cookies`, etc.) are accepted and mapped to the new implementation. +### Behaviour changes when model/dataset writes moved to OpenAPI + +Model and dataset creation and settings now use the `/openapi/v1` endpoints. +Inputs those endpoints cannot express keep using the legacy ones, so existing +calls continue to work; three observable differences remain: + +| Change | Detail | +|--------|--------| +| Gated repositories in metadata | The historical `Visibility` enum is preserved. New OpenAPI `"gated"` responses map to `Visibility.PRIVATE` with `gated=True`, rather than leaking a raw string as in 0.5.1 against that response. Older payloads without `visibility` could instead be misclassified as PUBLIC from `private: false`; both parsing cases are handled. | +| Default license | When `license` is omitted the Hub applies its own default (`apache-2.0`) instead of the SDK sending `Apache-2.0`. Only affects calls that take the OpenAPI route. | +| `LegacyHubApi.create_dataset(description=...)` | The argument was previously accepted and then dropped; it is now sent. | + +Unchanged on purpose: + +- **`gated_mode=True` / `gated_mode=False`** keep the exact legacy request + (`Visibility=PRIVATE` plus `ProtectedMode=1`/`2`), including warn-and-ignore for + explicit public/internal visibility and implicit private for either boolean + when visibility is omitted. `ms-hub create --gated/--no-gated` stays unchanged. +- High-level `approval_mode`, `gated`, `visibility="gated"` and string-valued + `gated_mode` are not supported. The removed request aliases must not leak into + legacy extra fields. Native OpenAPI payload/response fields remain unchanged. +- `delete_files()` still commits deletions directly, so it keeps glob support + and has no 100-path ceiling. +- `visibility="internal"`, licenses outside the OpenAPI enum, a `task` on a + dataset, and extra request fields all still reach the legacy endpoint. +- The AIGC creation path in `modelscope_hub.compat` + (`create_model(aigc_model=...)`, `create_model_tag(aigc_model=...)`) is + untouched. + --- ## Development diff --git a/src/modelscope_hub/__init__.py b/src/modelscope_hub/__init__.py index b6dc9c4..1594e8a 100644 --- a/src/modelscope_hub/__init__.py +++ b/src/modelscope_hub/__init__.py @@ -19,7 +19,18 @@ ) from .api import HubApi from .config import HubConfig, get_default_config, set_default_config -from .constants import License, RepoType, StudioVisibility, TokenScope, Visibility +from .constants import ( + AigcModelType, + DatasetCategory, + GatedMode, + License, + ModelCategory, + RepoType, + RepoVisibility, + StudioVisibility, + TokenScope, + Visibility, +) from .errors import ( APIError, AuthenticationError, @@ -75,8 +86,13 @@ "get_default_config", "set_default_config", # Enums + "AigcModelType", + "DatasetCategory", + "GatedMode", "License", + "ModelCategory", "RepoType", + "RepoVisibility", "StudioVisibility", "TokenScope", "Visibility", diff --git a/src/modelscope_hub/_openapi.py b/src/modelscope_hub/_openapi.py index 7217f09..46bb777 100644 --- a/src/modelscope_hub/_openapi.py +++ b/src/modelscope_hub/_openapi.py @@ -44,7 +44,9 @@ API_TIMEOUT, DOWNLOAD_CHUNK_SIZE, DOWNLOAD_RETRY_MAX_DELAY_SECONDS, + OPENAPI_DELETE_FILES_MAX_PATHS, OPENAPI_PREFIX, + OPTIONS_CACHE_TTL_SECONDS, TokenScope, ) from .errors import ( @@ -60,13 +62,22 @@ ) from .types import ( CreateAgentIdentityPayload, + CreateDatasetPayload, + CreateModelPayload, CreateSkillPayload, CreateStudioPayload, + DeleteFilesPayload, DeployMcpServerPayload, PauseAgentPayload, ResetAgentKeyPairPayload, + SetDatasetCategoryPayload, + SetModelCategoryPayload, TokenSignPayload, UpdateAgentIdentityPayload, + UpdateDatasetCategoryPayload, + UpdateDatasetSettingsPayload, + UpdateModelCategoryPayload, + UpdateModelSettingsPayload, UpdateSkillSettingsPayload, UpdateStudioSettingsPayload, ) @@ -124,6 +135,27 @@ def _validate_choice(name: str, value: str | None, allowed: tuple[str, ...]) -> raise InvalidParameter(f"{name} must be one of {', '.join(allowed)} (got {value!r}).") +def _validated_delete_files(payload: Mapping[str, Any]) -> dict[str, Any]: + """Check a delete-files payload against the limits the endpoint declares. + + The path count is bounded at both ends, so an empty list and an oversized + one are both rejected before the request goes out -- the server answers an + opaque 400 for either. + """ + body = dict(payload) + paths = body.get("file_paths") + if not isinstance(paths, list) or not paths: + raise InvalidParameter("file_paths must be a non-empty list of repository-relative file paths.") + if len(paths) > OPENAPI_DELETE_FILES_MAX_PATHS: + raise InvalidParameter( + f"file_paths accepts at most {OPENAPI_DELETE_FILES_MAX_PATHS} paths per request " + f"(got {len(paths)}). Use HubApi.delete_files() for larger batches." + ) + if len(set(paths)) != len(paths): + raise InvalidParameter("file_paths must not contain duplicate paths.") + return body + + def _as_wire_bool(value: bool | None) -> str | None: """Render a tri-state flag the way query strings expect (``true``/``false``).""" if value is None: @@ -184,6 +216,26 @@ def _as_wire_bool(value: bool | None) -> str | None: "addStudioVariable": ("add_studio_variable", TokenScope.WRITE), "updateStudioVariable": ("update_studio_variable", TokenScope.WRITE), "deleteStudioVariable": ("delete_studio_variable", TokenScope.WRITE), + # -- Models ------------------------------------------------------------- + "listModels": ("list_models", TokenScope.READ), + "createModel": ("create_model", TokenScope.WRITE), + "getModel": ("get_model", TokenScope.READ), + "setModelCategory": ("set_model_category", TokenScope.WRITE), + "updateModelCategory": ("update_model_category", TokenScope.WRITE), + "updateModelSettings": ("update_model_settings", TokenScope.WRITE), + "deleteModelFiles": ("delete_model_files", TokenScope.WRITE), + "getModelOptions": ("get_model_options", TokenScope.READ), + "listAigcModelOptions": ("list_aigc_model_options", TokenScope.READ), + "listScienceModelOptions": ("list_science_model_options", TokenScope.READ), + # -- Datasets ----------------------------------------------------------- + "listDatasets": ("list_datasets", TokenScope.READ), + "createDataset": ("create_dataset", TokenScope.WRITE), + "getDataset": ("get_dataset", TokenScope.READ), + "setDatasetCategory": ("set_dataset_category", TokenScope.WRITE), + "updateDatasetCategory": ("update_dataset_category", TokenScope.WRITE), + "updateDatasetSettings": ("update_dataset_settings", TokenScope.WRITE), + "deleteDatasetFiles": ("delete_dataset_files", TokenScope.WRITE), + "listScienceDatasetOptions": ("list_science_dataset_options", TokenScope.READ), } @@ -241,6 +293,11 @@ def __init__( # Tri-state cache for whether this deployment serves ``GET /mcp/servers``. # ``None`` means "not probed yet"; see :meth:`list_mcp_servers`. self._mcp_list_supports_get: bool | None = None + # ``path -> (monotonic timestamp, payload)`` for the ``*/options`` + # endpoints. Guarded by a lock because one client is shared across the + # worker threads that upload and download. + self._options_cache: dict[str, tuple[float, Any]] = {} + self._options_cache_lock = threading.Lock() self._read_state = threading.local() self._read_session = requests.Session() self._read_session.headers.update(self._session.headers) @@ -714,9 +771,153 @@ def list_models( return self._request("GET", "/models", params=params, require_token=False) def get_model(self, owner: str, repo_name: str) -> JSON: - """``GET /models/{owner}/{repo_name}`` — fetch a model's metadata.""" + """``GET /models/{owner}/{repo_name}`` — fetch a model's metadata. + + The payload is a ``ModelDetail``: the common repository fields plus + ``model_category`` and, for a model in a zone, that zone's fields only + (AIGC architecture and ``versions``, or the science subject areas). + Fields belonging to the other zones are absent rather than null. + """ return self._request("GET", f"/models/{owner}/{repo_name}", require_token=False) + # ------------------------------------------------------------------ + # Model / dataset enum options + # + # These endpoints publish product configuration and take no credentials. + # Their values change on a release cadence, so repeated reads within one + # process are served from a short-lived cache (see + # :data:`OPTIONS_CACHE_TTL_SECONDS`); a caller that sets a category no + # longer re-fetches the whole enum tree on every call. + # ------------------------------------------------------------------ + def _cached_options(self, path: str) -> JSON: + """Fetch an options endpoint, reusing a recent response when allowed.""" + ttl = OPTIONS_CACHE_TTL_SECONDS + if ttl <= 0: + return self._request("GET", path, require_token=False) + now = time.monotonic() + with self._options_cache_lock: + entry = self._options_cache.get(path) + if entry is not None and now - entry[0] < ttl: + return entry[1] + payload = self._request("GET", path, require_token=False) + with self._options_cache_lock: + self._options_cache[path] = (time.monotonic(), payload) + return payload + + def clear_options_cache(self) -> None: + """Drop every cached ``*/options`` response.""" + with self._options_cache_lock: + self._options_cache.clear() + + def list_aigc_model_options(self) -> JSON: + """``GET /model/aigc/options`` — AIGC zone enums. + + Returns ``aigc_types``, the ``base_architectures`` cascade (first-level + architecture plus ``subtypes``), and the ``content_styles`` / + ``content_functions`` / ``content_elements`` triples. Every option is a + ``{type, id, label}`` triple; submit ``id``, display ``label``. + """ + return self._cached_options("/model/aigc/options") + + def list_science_model_options(self) -> JSON: + """``GET /model/science/options`` — science model subject-area tree.""" + return self._cached_options("/model/science/options") + + def get_model_options(self) -> JSON: + """``GET /models/options`` — the task and license option lists. + + ``tasks[].id`` is what ``task`` accepts on create and settings, and what + ``filter.task`` accepts when listing; ``license[].id`` is what + ``license`` accepts. Needs no credentials. + """ + return self._cached_options("/models/options") + + # ------------------------------------------------------------------ + # Model writes + # ------------------------------------------------------------------ + def create_model(self, payload: CreateModelPayload | Mapping[str, Any]) -> JSON: + """``POST /models`` — create a general model repository. + + Accepts general metadata only; the new repository is ``base``. Zone + fields go through :meth:`set_model_category` after the weights are + uploaded. Unknown fields are rejected by the server with a 400. + """ + return self._request("POST", "/models", json_body=dict(payload), required_scope=TokenScope.WRITE) + + def set_model_category( + self, + owner: str, + repo_name: str, + payload: SetModelCategoryPayload | Mapping[str, Any], + ) -> JSON: + """``PUT /models/{owner}/{repo_name}/category`` — set or change the zone. + + ``PUT`` because this replaces the zone wholesale: the required fields + follow from the target ``model_category`` alone, and moving to ``base`` + clears every zone field. Partial edits within a zone use + :meth:`update_model_category`. + """ + return self._request( + "PUT", + f"/models/{owner}/{repo_name}/category", + json_body=dict(payload), + required_scope=TokenScope.WRITE, + ) + + def update_model_category( + self, + owner: str, + repo_name: str, + payload: UpdateModelCategoryPayload | Mapping[str, Any], + ) -> JSON: + """``PATCH /models/{owner}/{repo_name}/category`` — edit zone metadata. + + The server reads the current zone from its own database, so the payload + must not carry ``model_category``. AIGC version fields are addressed by + the numeric ``version_id``. + """ + return self._request( + "PATCH", + f"/models/{owner}/{repo_name}/category", + json_body=dict(payload), + required_scope=TokenScope.WRITE, + ) + + def update_model_settings( + self, + owner: str, + repo_name: str, + settings: UpdateModelSettingsPayload | Mapping[str, Any], + ) -> JSON: + """``PATCH /models/{owner}/{repo_name}/settings`` — general settings. + + Omitted fields are unchanged; explicit nulls clear. Zone fields are not + accepted here. + """ + return self._request( + "PATCH", + f"/models/{owner}/{repo_name}/settings", + json_body=dict(settings), + required_scope=TokenScope.WRITE, + ) + + def delete_model_files( + self, + owner: str, + repo_name: str, + payload: DeleteFilesPayload | Mapping[str, Any], + ) -> JSON: + """``DELETE /models/{owner}/{repo_name}/files`` — one atomic commit. + + See :meth:`delete_dataset_files` for the shared constraints. + """ + return self._request( + "DELETE", + f"/models/{owner}/{repo_name}/files", + json_body=_validated_delete_files(payload), + required_scope=TokenScope.WRITE, + ) + # ================================================================== # Datasets # ================================================================== @@ -746,9 +947,103 @@ def list_datasets( return self._request("GET", "/datasets", params=params, require_token=False) def get_dataset(self, owner: str, repo_name: str) -> JSON: - """``GET /datasets/{owner}/{repo_name}`` — fetch a dataset's metadata.""" + """``GET /datasets/{owner}/{repo_name}`` — fetch a dataset's metadata. + + The payload is a ``DatasetDetail``: the common repository fields plus + ``dataset_category``, ``login_required``, and -- only for a science + dataset -- the subject areas and cover image. + """ return self._request("GET", f"/datasets/{owner}/{repo_name}", require_token=False) + def list_science_dataset_options(self) -> JSON: + """``GET /dataset/science/options`` — science dataset subject-area tree.""" + return self._cached_options("/dataset/science/options") + + # ------------------------------------------------------------------ + # Dataset writes + # ------------------------------------------------------------------ + def create_dataset(self, payload: CreateDatasetPayload | Mapping[str, Any]) -> JSON: + """``POST /datasets`` — create a general dataset repository. + + The new repository is ``base``; science fields go through + :meth:`set_dataset_category`. + """ + return self._request("POST", "/datasets", json_body=dict(payload), required_scope=TokenScope.WRITE) + + def set_dataset_category( + self, + owner: str, + repo_name: str, + payload: SetDatasetCategoryPayload | Mapping[str, Any], + ) -> JSON: + """``PUT /datasets/{owner}/{repo_name}/category`` — set or change the zone.""" + return self._request( + "PUT", + f"/datasets/{owner}/{repo_name}/category", + json_body=dict(payload), + required_scope=TokenScope.WRITE, + ) + + def update_dataset_category( + self, + owner: str, + repo_name: str, + payload: UpdateDatasetCategoryPayload | Mapping[str, Any], + ) -> JSON: + """``PATCH /datasets/{owner}/{repo_name}/category`` — edit science fields. + + The payload must not carry ``dataset_category``; the server reads the + current zone itself and rejects the field. + """ + return self._request( + "PATCH", + f"/datasets/{owner}/{repo_name}/category", + json_body=dict(payload), + required_scope=TokenScope.WRITE, + ) + + def update_dataset_settings( + self, + owner: str, + repo_name: str, + settings: UpdateDatasetSettingsPayload | Mapping[str, Any], + ) -> JSON: + """``PATCH /datasets/{owner}/{repo_name}/settings`` — general settings. + + Omitted fields are unchanged; explicit nulls clear. Zone fields and + ``login_required`` are not accepted here. + """ + return self._request( + "PATCH", + f"/datasets/{owner}/{repo_name}/settings", + json_body=dict(settings), + required_scope=TokenScope.WRITE, + ) + + def delete_dataset_files( + self, + owner: str, + repo_name: str, + payload: DeleteFilesPayload | Mapping[str, Any], + ) -> JSON: + """``DELETE /datasets/{owner}/{repo_name}/files`` — one atomic commit. + + Takes 1 to 100 distinct repo-relative file paths -- no directories, no + traversal -- and ``revision`` must be a branch. The request is + all-or-nothing: one unresolvable path fails every deletion. + + :meth:`HubApi.delete_files` does *not* route here. It commits deletions + directly, which lifts the 100-path ceiling and supports glob patterns; + this method exists for callers that want the endpoint's ``commit_sha`` + and its strict all-or-nothing guarantee. + """ + return self._request( + "DELETE", + f"/datasets/{owner}/{repo_name}/files", + json_body=_validated_delete_files(payload), + required_scope=TokenScope.WRITE, + ) + # ================================================================== # Files # ================================================================== diff --git a/src/modelscope_hub/api.py b/src/modelscope_hub/api.py index 99035b0..15e38d4 100644 --- a/src/modelscope_hub/api.py +++ b/src/modelscope_hub/api.py @@ -27,7 +27,7 @@ import time from collections.abc import Callable, Iterable, Mapping from pathlib import Path -from typing import Any, BinaryIO, TypeAlias +from typing import Any, BinaryIO, TypeAlias, TypeVar from urllib.parse import urlparse from requests.cookies import RequestsCookieJar @@ -42,14 +42,32 @@ from ._upload import UploadManager from .agent_idp import sign_agent_token_request from .config import HubConfig, get_default_config -from .constants import DEFAULT_ENDPOINT, RepoType, StudioVisibility, Visibility +from .constants import ( + DEFAULT_ENDPOINT, + OPENAPI_CREATE_DATASET_FIELDS, + OPENAPI_CREATE_MODEL_FIELDS, + OPENAPI_MODEL_LICENSES, + OPENAPI_VERSION_NAME_MAX_LENGTH, + WEIGHT_FILE_SUFFIXES, + AigcModelType, + DatasetCategory, + ModelCategory, + RepoType, + RepoVisibility, + StrEnum, + StudioVisibility, + Visibility, +) from .errors import ( + AlreadyExistsError, AuthenticationError, HubError, InvalidParameter, NotExistError, NotSupportedError, PermissionDeniedError, + ServerError, + is_repo_exists_error, ) from .types import ( AgentIdConfiguration, @@ -76,11 +94,19 @@ RepoTypeLike: TypeAlias = "str | RepoType" +# Any of the string enums the category payloads validate against. +_StrEnumT = TypeVar("_StrEnumT", bound=StrEnum) + # Routing tables — declarative dispatch keeps :class:`HubApi` free of long # if/elif chains and makes adding new repo types a one-line change. _CREATABLE_TYPES: frozenset[RepoType] = frozenset({RepoType.MODEL, RepoType.DATASET, RepoType.STUDIO, RepoType.SKILL}) -_OPENAPI_CREATE_TYPES: frozenset[RepoType] = frozenset({RepoType.STUDIO, RepoType.SKILL}) +_OPENAPI_CREATE_TYPES: frozenset[RepoType] = frozenset( + {RepoType.MODEL, RepoType.DATASET, RepoType.STUDIO, RepoType.SKILL} +) +# Repo kinds whose OpenAPI create endpoint can be bypassed for inputs it cannot +# express. Studio and Skill have no legacy create endpoint, so they are absent. +_LEGACY_CREATE_FALLBACK_TYPES: frozenset[RepoType] = frozenset({RepoType.MODEL, RepoType.DATASET}) _OPENAPI_DETAIL_TYPES: frozenset[RepoType] = frozenset( {RepoType.MODEL, RepoType.DATASET, RepoType.STUDIO, RepoType.SKILL} ) @@ -118,6 +144,11 @@ # These MUST NOT be overridden via extra kwargs to avoid silent conflicts. _RESERVED_EXTRA_FIELDS: frozenset[str] = frozenset({"Path", "Owner", "Name"}) +# Settings the model/dataset settings endpoints report but cannot change. +# ``login_required`` appears in a dataset's detail payload, yet no request +# schema accepts it, so it can only be changed through the web UI. +_UNSETTABLE_FIELDS: frozenset[str] = frozenset({"login_required"}) + def _unwrap_list(payload: Any, keys: tuple[str, ...]) -> list[dict]: """Pull a list out of an OpenAPI payload, tolerating either shape. @@ -423,31 +454,46 @@ def _repo_info_from_payload( for key, value in data.items(): normalised[aliases.get(key, key)] = value - # The OpenAPI surface uses ``private`` bool for visibility. - # gated is orthogonal and does not affect visibility mapping. + # Normalize native access modes to the historical SDK enum. The server + # reports private=false for gated repos, so the private flag alone is insufficient. raw_visibility = normalised.get("visibility") if isinstance(raw_visibility, str): - # Studio payloads report visibility as a string enum. ``public`` and - # ``private`` have integer equivalents; ``protected`` (app public, - # code hidden) does not, so it is preserved verbatim rather than - # forced into a value that would misrepresent it. + # Two string encodings arrive here. Studio payloads use + # ``public``/``protected``/``private``; model and dataset detail + # payloads use ``public``/``private``/``gated``. ``gated`` must not + # fall through as a raw string: ``visibility`` is typed as the + # integer :class:`Visibility`, and leaking a ``str`` would break + # ``.label``, ``int()`` and equality against the enum -- including + # in the compat dict, which the legacy SDK reads as an integer. + # + # A gated repository maps to PRIVATE because that is what it is on + # the legacy wire (``Visibility=PRIVATE`` plus ``ProtectedMode=1``); + # the approval mode itself is carried separately by ``gated`` and + # ``gated_mode``. The server reports ``private: false`` for gated + # repos, so without this branch they would be reported as PUBLIC. + repo_visibility = RepoVisibility.parse(raw_visibility) studio_visibility = StudioVisibility.parse(raw_visibility) - if studio_visibility is StudioVisibility.PUBLIC: + if repo_visibility is RepoVisibility.GATED: + normalised["visibility"] = Visibility.PRIVATE + normalised.setdefault("gated", True) + elif studio_visibility is StudioVisibility.PUBLIC: normalised["visibility"] = Visibility.PUBLIC elif studio_visibility is StudioVisibility.PRIVATE: normalised["visibility"] = Visibility.PRIVATE elif studio_visibility is not None: + # ``protected`` (app public, code hidden) has no integer + # equivalent, so it is preserved verbatim rather than forced + # into a value that would misrepresent it. normalised["visibility"] = studio_visibility.value elif raw_visibility is None: private_flag = normalised.get("private") - if isinstance(private_flag, bool): - if private_flag: - normalised["visibility"] = Visibility.PRIVATE - else: - normalised["visibility"] = Visibility.PUBLIC - elif normalised.get("gated"): - # Rule 4: visibility unknown + gated=True → imply PRIVATE + if normalised.get("gated"): + # Checked before ``private``: the server reports ``private: + # false`` for a gated repo, so trusting that flag first would + # report an application-gated repo as public. normalised["visibility"] = Visibility.PRIVATE + elif isinstance(private_flag, bool): + normalised["visibility"] = Visibility.PRIVATE if private_flag else Visibility.PUBLIC # The OpenAPI list endpoints return ``id`` as "owner/name". # Split it so the computed ``repo_id`` property works. @@ -813,6 +859,9 @@ def create_repo( chinese_name: str | None = None, description: str | None = None, gated_mode: bool | None = None, + license_name: str | None = None, + license_link: str | None = None, + task: str | None = None, **extra: Any, ) -> RepoInfo: """Create a new repository. @@ -820,7 +869,9 @@ def create_repo( Routing is decided by ``repo_type``: * ``studio`` / ``skill`` → OpenAPI ``POST /studios`` / ``POST /skills`` - * ``model`` / ``dataset`` → legacy ``POST /api/v1/{type}s`` + * ``model`` / ``dataset`` → OpenAPI ``POST /models`` / ``POST /datasets``, + falling back to the legacy endpoint for inputs the OpenAPI schema + cannot express (see :meth:`_legacy_create_reason`) Parameters ---------- @@ -830,18 +881,31 @@ def create_repo( One of ``"model"``, ``"dataset"``, ``"studio"``, ``"skill"``. visibility : int, str or Visibility, optional Visibility level. Accepts the integer wire encoding, a label - (``"public"`` / ``"private"``) or a :class:`Visibility` value. - Defaults to public. + (``"public"`` / ``"private"`` / ``"internal"``) or a + :class:`Visibility` value. Defaults to public. license : str, optional - SPDX-style license identifier (e.g. ``"apache-2.0"``). + SPDX-style license identifier (e.g. ``"apache-2.0"``). Values the + OpenAPI model schema does not list are still accepted, via the + legacy endpoint. chinese_name : str, optional - Chinese display name shown on the Hub UI. + Display name shown on the Hub UI. description : str, optional Short description of the repository. gated_mode : bool, optional - Enable gated (application-based download) mode for private repos. - True = gated, False = normal private. Only effective when - visibility is PRIVATE; ignored otherwise. + Historical application-access switch, not deprecated. Effective + only for private repositories; explicit public/internal visibility + takes precedence and the switch is warned about and ignored. + Either explicit boolean with omitted visibility implies private. + These calls retain the legacy ProtectedMode=1/2 request format. + This boolean does not select an approval policy. + license_name : str, optional + Custom license name. Required when ``license="other"``. + license_link : str, optional + Custom license URL, honoured when ``license="other"``. When omitted + the Hub creates a blank ``LICENSE.md``. + task : str, optional + Model task identifier, as published by + :meth:`list_model_options`. Models only. **extra : Any Additional fields forwarded verbatim to the underlying client. @@ -853,7 +917,8 @@ def create_repo( Raises ------ InvalidParameter - When ``repo_id`` does not have the ``owner/name`` shape. + When ``repo_id`` does not have the ``owner/name`` shape, or when + ``visibility`` is a value the repo type does not support. AuthenticationError When the token is missing or invalid. @@ -871,7 +936,7 @@ def create_repo( >>> info.repo_id 'alice/llama-7b-finetuned' - Create a private gated dataset: + Create a private dataset with application-gated access: >>> api.create_repo("alice/my-data", "dataset", visibility="private", gated_mode=True) @@ -879,6 +944,7 @@ def create_repo( >>> api.create_repo("alice/chat-demo", repo_type="studio", visibility="public") """ + self._reject_removed_gating_parameters(extra) rt = self._normalize_repo_type(repo_type) if rt not in _CREATABLE_TYPES: supported = ", ".join(sorted(t.value for t in _CREATABLE_TYPES)) @@ -891,44 +957,280 @@ def create_repo( if license is not None: license = _LICENSE_DISPLAY_TO_SPDX.get(license, license) - if rt in _OPENAPI_CREATE_TYPES: - is_private = vis is not None and vis == int(Visibility.PRIVATE) - if rt is RepoType.STUDIO: - payload: dict[str, Any] = { - "owner": owner, - "repo_name": name, - } - if studio_visibility is not None: - # ``visibility`` is the expressive form (it is the only way to - # ask for ``protected``); ``private`` is sent alongside it for - # servers that predate the tri-state field. The two are - # consistent by construction, so they cannot disagree. - payload["visibility"] = studio_visibility.value - payload["private"] = studio_visibility is StudioVisibility.PRIVATE - elif vis is not None: - payload["private"] = is_private - if chinese_name is not None: - payload["display_name"] = chinese_name - else: - payload = { - "owner": owner, - "skill_name": name, - } - if vis is not None: - payload["private"] = is_private - if chinese_name is not None: - payload["display_name"] = chinese_name - if license is not None: - payload["license"] = license - if description is not None: - payload["description"] = description - # Only Studios get the Studio field normalisation: a Skill has no - # cover image and no visibility field, so rewriting those keys on a - # Skill payload would invent fields its endpoint does not accept. - payload.update(_normalise_studio_fields(extra) if rt is RepoType.STUDIO else extra) - data = self.openapi.create_studio(payload) if rt is RepoType.STUDIO else self.openapi.create_skill(payload) - return self._repo_info_from_payload(data, rt, owner_hint=owner, name_hint=name) + if rt in _LEGACY_CREATE_FALLBACK_TYPES: + return self._create_model_or_dataset( + rt, + owner, + name, + visibility=vis, + license=license, + chinese_name=chinese_name, + description=description, + gated_mode=gated_mode, + license_name=license_name, + license_link=license_link, + task=task, + extra=extra, + ) + + is_private = vis is not None and vis == int(Visibility.PRIVATE) + if rt is RepoType.STUDIO: + payload: dict[str, Any] = { + "owner": owner, + "repo_name": name, + } + if studio_visibility is not None: + # ``visibility`` is the expressive form (it is the only way to + # ask for ``protected``); ``private`` is sent alongside it for + # servers that predate the tri-state field. The two are + # consistent by construction, so they cannot disagree. + payload["visibility"] = studio_visibility.value + payload["private"] = studio_visibility is StudioVisibility.PRIVATE + elif vis is not None: + payload["private"] = is_private + if chinese_name is not None: + payload["display_name"] = chinese_name + else: + payload = { + "owner": owner, + "skill_name": name, + } + if vis is not None: + payload["private"] = is_private + if chinese_name is not None: + payload["display_name"] = chinese_name + if license is not None: + payload["license"] = license + if description is not None: + payload["description"] = description + # Only Studios get the Studio field normalisation: a Skill has no + # cover image and no visibility field, so rewriting those keys on a + # Skill payload would invent fields its endpoint does not accept. + payload.update(_normalise_studio_fields(extra) if rt is RepoType.STUDIO else extra) + data = self.openapi.create_studio(payload) if rt is RepoType.STUDIO else self.openapi.create_skill(payload) + return self._repo_info_from_payload(data, rt, owner_hint=owner, name_hint=name) + @staticmethod + def _reject_removed_gating_parameters(parameters: Mapping[str, Any]) -> None: + """Reject removed request parameters before kwargs can reach a backend.""" + removed = sorted({"approval_mode", "gated"} & set(parameters)) + if removed: + raise TypeError(f"Unexpected keyword argument(s): {', '.join(removed)}; use gated_mode=True/False.") + + @staticmethod + def _legacy_create_reason( + rt: RepoType, + *, + visibility: int | None, + license: str | None, + extra: Mapping[str, Any], + gated_mode: bool | None = None, + task: str | None = None, + ) -> str | None: + """Explain why these inputs cannot go through the OpenAPI create endpoint. + + The OpenAPI schemas are closed: ``visibility`` has no ``internal``, + ``POST /models`` restricts ``license`` to a published set, and both + endpoints answer 400 for any field they do not declare. The legacy + endpoint accepts all three, so rather than failing a call that works + today, these inputs keep using it. Returns ``None`` when the OpenAPI + endpoint can serve the request. + """ + if gated_mode is not None: + # Retain the historical boolean wire contract, not a new policy selector. + return "historical gated_mode boolean keeps the legacy ProtectedMode wire" + if visibility is not None and visibility == int(Visibility.INTERNAL): + return "visibility=internal is not part of the OpenAPI visibility enum" + if rt is RepoType.MODEL and license is not None and license not in OPENAPI_MODEL_LICENSES: + return f"license={license!r} is not in the OpenAPI model license enum" + if rt is not RepoType.MODEL and task is not None: + # Only the model schema declares ``task``. The legacy endpoint used + # to receive it verbatim through ``**extra``, so route there rather + # than reject a call that used to work. + return f"task is not declared by the OpenAPI {rt.value} create schema" + allowed = OPENAPI_CREATE_MODEL_FIELDS if rt is RepoType.MODEL else OPENAPI_CREATE_DATASET_FIELDS + unknown = sorted(set(extra) - allowed) + if unknown: + return f"field(s) {unknown} are not declared by the OpenAPI create schema" + return None + + def _create_model_or_dataset( + self, + rt: RepoType, + owner: str, + name: str, + *, + visibility: int | None, + license: str | None, + chinese_name: str | None, + description: str | None, + gated_mode: bool | None, + license_name: str | None, + license_link: str | None, + task: str | None, + extra: Mapping[str, Any], + ) -> RepoInfo: + """Create a model or dataset, preferring OpenAPI over the legacy endpoint.""" + if gated_mode is not None and not isinstance(gated_mode, bool): + raise InvalidParameter("gated_mode must be True, False or None, not an approval-policy string.") + reason = self._legacy_create_reason( + rt, + visibility=visibility, + license=license, + extra=extra, + gated_mode=gated_mode, + task=task, + ) + if reason is None: + try: + return self._create_via_openapi( + rt, + owner, + name, + visibility=visibility, + license=license, + chinese_name=chinese_name, + description=description, + license_name=license_name, + license_link=license_link, + task=task, + extra=extra, + ) + except (NotExistError, ServerError) as exc: + # 404 means this deployment has not published the endpoint yet; + # 501 means it knows the route but will not serve it. Both are + # "not available here", so fall through rather than fail. + if exc.status_code not in (404, 501): + raise + reason = f"the OpenAPI create endpoint answered {exc.status_code}" + logger.debug("Creating %s %s/%s through the legacy endpoint: %s", rt.value, owner, name, reason) + return self._create_via_legacy( + rt, + owner, + name, + visibility=visibility, + license=license, + chinese_name=chinese_name, + description=description, + gated_mode=gated_mode, + # ``task`` / ``license_name`` / ``license_link`` are named parameters + # only because the OpenAPI schema declares them. The legacy endpoint + # has always received them through ``**extra`` under exactly these + # keys, so they are restored rather than renamed -- a legacy request + # must not change shape just because the signature grew. + extra={ + **extra, + **({"task": task} if task is not None else {}), + **({"license_name": license_name} if license_name is not None else {}), + **({"license_link": license_link} if license_link is not None else {}), + }, + ) + + def _create_via_openapi( + self, + rt: RepoType, + owner: str, + name: str, + *, + visibility: int | None, + license: str | None, + chinese_name: str | None, + description: str | None, + license_name: str | None, + license_link: str | None, + task: str | None, + extra: Mapping[str, Any], + ) -> RepoInfo: + """Create a model or dataset via ``POST /models`` / ``POST /datasets``.""" + payload: dict[str, Any] = {"owner": owner, "repo_name": name} + wire_visibility = self._openapi_visibility(rt, visibility) + if wire_visibility is not None: + payload["visibility"] = wire_visibility.value + if chinese_name is not None: + payload["display_name"] = chinese_name + # ``license`` is omitted rather than defaulted: the server's own default + # (``apache-2.0``) is inside the enum, whereas the legacy fallback value + # (``"Apache-2.0"``) is not and would be rejected. + if license is not None: + payload["license"] = license + if license_name is not None: + payload["license_name"] = license_name + if license_link is not None: + payload["license_link"] = license_link + if description is not None: + payload["description"] = description + if task is not None: + if rt is not RepoType.MODEL: + raise InvalidParameter("task is only supported for repo_type='model'.") + payload["task"] = task + payload.update(extra) + + try: + data = self.openapi.create_model(payload) if rt is RepoType.MODEL else self.openapi.create_dataset(payload) + except InvalidParameter as exc: + # ``POST /datasets`` declares no 409, so a duplicate may arrive as a + # 400. Callers catch AlreadyExistsError, so reclassify rather than + # let a rename of an existing repo look like bad input. + if not isinstance(exc, AlreadyExistsError) and is_repo_exists_error(exc): + raise AlreadyExistsError( + str(exc.message or exc), + status_code=exc.status_code, + request_id=exc.request_id, + response_body=exc.response_body, + ) from exc + raise + info = self._repo_info_from_payload(data, rt, owner_hint=owner, name_hint=name) + # The create response carries only identity fields, while the legacy one + # echoed the settings back. Backfill from the request so a caller reading + # ``info.visibility`` after create does not see None where it saw a value. + # Backfill enum instances, not bare ints, after dataclass construction. + if info.visibility is None and wire_visibility is not None: + info.visibility = Visibility.PUBLIC if wire_visibility is RepoVisibility.PUBLIC else Visibility.PRIVATE + if info.license is None: + info.license = license + if info.description is None: + info.description = description + if info.gated is None: + info.gated = False + return info + + @staticmethod + def _openapi_visibility(rt: RepoType, visibility: int | None) -> RepoVisibility | None: + """Map historical ordinary visibility values to OpenAPI strings. + + None omits the field, retaining the public server default. Internal + visibility and explicit application switches are routed to legacy + before this helper is called. + """ + if visibility is None: + return None + if visibility == int(Visibility.PRIVATE): + return RepoVisibility.PRIVATE + if visibility == int(Visibility.PUBLIC): + return RepoVisibility.PUBLIC + raise InvalidParameter(f"visibility={visibility!r} is not supported by the OpenAPI {rt.value} create endpoint.") + + def _create_via_legacy( + self, + rt: RepoType, + owner: str, + name: str, + *, + visibility: int | None, + license: str | None, + chinese_name: str | None, + description: str | None, + gated_mode: bool | None, + extra: Mapping[str, Any], + ) -> RepoInfo: + """Create a model or dataset via the legacy ``POST /api/v1/{type}s``. + + The body is built in the order this endpoint has always received it, and + ``extra`` is appended last and verbatim. Callers reach this path because + the OpenAPI schema could not express their input, so the request they + get must be the one they got before the OpenAPI route existed. + """ + vis = visibility if rt is RepoType.DATASET: body: dict[str, Any] = { "Owner": owner, @@ -948,9 +1250,8 @@ def create_repo( if description is not None: body["Description"] = description - # gated_mode → ProtectedMode wire field (1=gated, 2=off). - # gated only effective with PRIVATE; when vis=None + gated=True, - # implicitly set visibility to PRIVATE (user intent: gated repo). + # Preserve v0.5.1: either explicit boolean with no visibility implies + # PRIVATE; public/internal visibility wins and ignores the switch. if gated_mode is not None: if vis is None: vis = int(Visibility.PRIVATE) @@ -1044,6 +1345,470 @@ def get_repo( return self._repo_info_from_payload(data, rt, owner_hint=owner, name_hint=name) + def get_model(self, repo_id: str) -> dict: + """Fetch a model's full detail payload, zone fields included. + + Returns the server's ``ModelDetail`` verbatim (snake_case keys) rather + than a :class:`RepoInfo`. The zone-specific fields -- AIGC architecture + and ``versions``, or the science subject areas -- have no place on the + dataclass, and :meth:`get_repo` would silently drop them. + + Which keys are present depends on ``model_category``: ``base`` carries + no zone fields, and the fields of the zones a model is *not* in are + absent rather than null. Read them with ``.get()``. + + Parameters + ---------- + repo_id : str + Canonical ``owner/name`` identifier. + + Returns + ------- + dict + The ``ModelDetail`` payload. + + Examples + -------- + >>> detail = api.get_model("alice/my-anime-lora") + >>> detail["model_category"] + 'aigc' + >>> [v["version_id"] for v in detail.get("versions", [])] + [1070760] + """ + owner, name = self._parse_repo_id(repo_id) + data = self.openapi.get_model(owner, name) + return dict(data) if isinstance(data, Mapping) else {} + + def model_info(self, repo_id: str) -> dict: + """Alias of :meth:`get_model`.""" + return self.get_model(repo_id) + + def get_dataset(self, repo_id: str) -> dict: + """Fetch a dataset's full detail payload, zone fields included. + + The dataset counterpart of :meth:`get_model`; see there for why the raw + payload is returned instead of a :class:`RepoInfo`. Science datasets + additionally carry the subject areas and cover image. + + Examples + -------- + >>> detail = api.get_dataset("alice/rna-seq") + >>> detail["dataset_category"], detail.get("primary_subject_area") + ('science', 'life-sciences') + """ + owner, name = self._parse_repo_id(repo_id) + data = self.openapi.get_dataset(owner, name) + return dict(data) if isinstance(data, Mapping) else {} + + def dataset_info(self, repo_id: str) -> dict: + """Alias of :meth:`get_dataset`.""" + return self.get_dataset(repo_id) + + # ================================================================== + # Zone configuration enums + # ================================================================== + def list_aigc_model_options(self) -> dict: + """Return the enum values accepted when setting the AIGC category. + + Needs no credentials. Every option is a ``{type, id, label}`` triple: + pass ``id`` to :meth:`set_model_category`, show ``label`` to users. + + Returns + ------- + dict + ``aigc_types``, ``base_architectures`` (each with ``subtypes``), + ``content_styles``, ``content_functions``, ``content_elements``. + + Examples + -------- + >>> options = api.list_aigc_model_options() + >>> for arch in options["base_architectures"]: + ... print(arch["id"], [s["id"] for s in arch["subtypes"]]) + """ + data = self.openapi.list_aigc_model_options() + return dict(data) if isinstance(data, Mapping) else {} + + def list_science_model_options(self) -> dict: + """Return the science-model subject-area tree. Needs no credentials. + + Examples + -------- + >>> tree = api.list_science_model_options() + >>> tree["subject_areas"][0]["id"] + 'life-sciences' + """ + data = self.openapi.list_science_model_options() + return dict(data) if isinstance(data, Mapping) else {} + + def list_science_dataset_options(self) -> dict: + """Return the science-dataset subject-area tree. Needs no credentials.""" + data = self.openapi.list_science_dataset_options() + return dict(data) if isinstance(data, Mapping) else {} + + def list_model_options(self) -> dict: + """Return the valid model ``task`` and ``license`` values. + + Needs no credentials. Each entry is a ``{type, id, label}`` triple; pass + ``id`` to :meth:`create_repo` or :meth:`update_repo_settings`. + + Returns + ------- + dict + ``tasks`` and ``license`` lists. + + Examples + -------- + >>> options = api.list_model_options() + >>> [t["id"] for t in options["tasks"]][:2] + ['text-generation', 'image-captioning'] + """ + data = self.openapi.get_model_options() + return dict(data) if isinstance(data, Mapping) else {} + + # ================================================================== + # Zone membership (category) + # ================================================================== + def set_model_category( + self, + repo_id: str, + repo_type: RepoTypeLike = RepoType.MODEL, + *, + model_category: str | ModelCategory, + **fields: Any, + ) -> dict: + """Set or change which zone a model belongs to. + + Which fields are required follows from the *target* category, not from + the model's current one. Upload the weight files first: the endpoint + validates that each ``path_in_repo`` already exists. + + * ``base`` -- no other field; every zone field is cleared. + * ``aigc`` -- ``aigc_type``, ``base_model_architecture_type``, + ``base_model_architecture_subtype``, ``version_name``, and non-empty + ``weight_files`` and ``sample_images``. Creates a new version. + * ``science`` -- ``primary_subject_area`` and + ``secondary_specialization``; ``cover_image`` optional. + + Enum values come from :meth:`list_aigc_model_options` / + :meth:`list_science_model_options`; pass each option's ``id``. + + Parameters + ---------- + repo_id : str + Canonical ``owner/name`` identifier. + repo_type : str or RepoType, optional + Must be ``"model"``. Accepted for symmetry with the other methods. + model_category : str or ModelCategory + Target zone: ``"base"``, ``"aigc"`` or ``"science"``. + **fields : Any + Zone fields for the target category. + + Returns + ------- + dict + Repository identity (``id``, ``repo_name``, ``owner``, ``url``). + + Raises + ------ + NotSupportedError + When ``repo_type`` is not ``"model"``. + InvalidParameter + When ``model_category`` is unknown, or when a required AIGC field is + missing or malformed. + + Examples + -------- + >>> api.set_model_category( + ... "alice/my-anime-lora", + ... model_category="aigc", + ... aigc_type="LoRA", + ... base_model_architecture_type="Z_IMAGE_TURBO", + ... base_model_architecture_subtype="Z_IMAGE_TURBO", + ... version_name="v1.0", + ... weight_files=[{"path_in_repo": "model.safetensors"}], + ... sample_images=[{"url": "https://example.com/a.png", "prompt": "anime girl"}], + ... ) + + Move it back out of the zone: + + >>> api.set_model_category("alice/my-anime-lora", model_category="base") + """ + rt = self._normalize_repo_type(repo_type) + if rt is not RepoType.MODEL: + raise NotSupportedError(f"set_model_category only supports repo_type='model' (got {rt.value!r}).") + owner, name = self._parse_repo_id(repo_id) + category = self._coerce_enum(model_category, ModelCategory, "model_category") + payload: dict[str, Any] = {"model_category": category.value} + payload.update(fields) + self._validate_set_model_category(category, payload) + data = self.openapi.set_model_category(owner, name, payload) + return dict(data) if isinstance(data, Mapping) else {} + + def update_model_category( + self, + repo_id: str, + repo_type: RepoTypeLike = RepoType.MODEL, + *, + version_id: int | None = None, + version_name: str | None = None, + **fields: Any, + ) -> dict: + """Update a model's zone metadata in place, without changing the zone. + + The server reads the current zone from its own records, so the category + is neither needed nor accepted. Use :meth:`set_model_category` to move + between zones or to replace an existing version's weights. + + AIGC version fields (``trigger_words``, ``version_description``, + ``sample_images``) address a specific version. Give either its numeric + ``version_id`` or its ``version_name``, which is resolved to the id with + one extra detail read. Science updates need neither. + + Omitted fields are left unchanged; passing ``None`` clears a field. + + Parameters + ---------- + repo_id : str + Canonical ``owner/name`` identifier. + repo_type : str or RepoType, optional + Must be ``"model"``. + version_id : int, optional + Numeric version id, as reported by :meth:`get_model`. + version_name : str, optional + Version name to resolve into ``version_id``. Ignored when + ``version_id`` is given. + **fields : Any + Zone fields to update. + + Returns + ------- + dict + Repository identity. + + Raises + ------ + NotSupportedError + When ``repo_type`` is not ``"model"``. + InvalidParameter + When a field belongs to :meth:`set_model_category` instead, or when + ``version_name`` matches no version. + + Examples + -------- + >>> api.update_model_category( + ... "alice/my-anime-lora", + ... version_name="v1.0", + ... trigger_words=["anime style"], + ... ) + >>> api.update_model_category("alice/rna-predictor", secondary_specialization="dna") + """ + rt = self._normalize_repo_type(repo_type) + if rt is not RepoType.MODEL: + raise NotSupportedError(f"update_model_category only supports repo_type='model' (got {rt.value!r}).") + owner, name = self._parse_repo_id(repo_id) + payload = self._strip_category_discriminator(dict(fields), "model_category", "set_model_category") + self._reject_set_only_fields(payload) + if version_id is None and version_name is not None: + version_id = self._resolve_version_id(repo_id, version_name) + if version_id is not None: + payload["version_id"] = version_id + data = self.openapi.update_model_category(owner, name, payload) + return dict(data) if isinstance(data, Mapping) else {} + + def set_dataset_category( + self, + repo_id: str, + repo_type: RepoTypeLike = RepoType.DATASET, + *, + dataset_category: str | DatasetCategory, + **fields: Any, + ) -> dict: + """Set or change which zone a dataset belongs to. + + ``science`` requires a matching ``primary_subject_area`` and + ``secondary_specialization`` (ids from + :meth:`list_science_dataset_options`); ``base`` clears them. Note the + general value is ``base``, not ``basic``. + + Examples + -------- + >>> api.set_dataset_category( + ... "alice/rna-seq", + ... dataset_category="science", + ... primary_subject_area="life-sciences", + ... secondary_specialization="dna", + ... ) + """ + rt = self._normalize_repo_type(repo_type) + if rt is not RepoType.DATASET: + raise NotSupportedError(f"set_dataset_category only supports repo_type='dataset' (got {rt.value!r}).") + owner, name = self._parse_repo_id(repo_id) + category = self._coerce_enum(dataset_category, DatasetCategory, "dataset_category") + payload: dict[str, Any] = {"dataset_category": category.value} + payload.update(fields) + data = self.openapi.set_dataset_category(owner, name, payload) + return dict(data) if isinstance(data, Mapping) else {} + + def update_dataset_category( + self, + repo_id: str, + repo_type: RepoTypeLike = RepoType.DATASET, + **fields: Any, + ) -> dict: + """Update a dataset's science fields in place, without changing the zone. + + As with :meth:`update_model_category`, the category is read by the + server and is not accepted in the payload. Omitted fields are left + unchanged; ``None`` clears a field. + + Examples + -------- + >>> api.update_dataset_category("alice/rna-seq", secondary_specialization="medicine") + >>> api.update_dataset_category("alice/rna-seq", cover_image=None) + """ + rt = self._normalize_repo_type(repo_type) + if rt is not RepoType.DATASET: + raise NotSupportedError(f"update_dataset_category only supports repo_type='dataset' (got {rt.value!r}).") + owner, name = self._parse_repo_id(repo_id) + payload = self._strip_category_discriminator(dict(fields), "dataset_category", "set_dataset_category") + data = self.openapi.update_dataset_category(owner, name, payload) + return dict(data) if isinstance(data, Mapping) else {} + + # ------------------------------------------------------------------ + # Category payload checks + # + # Each rule below mirrors one the server enforces. They run locally so a + # request that is certain to fail reports the fix straight away instead of + # costing a round trip for a generic 400. Anything whose valid values the + # server owns (architectures, subject areas, tasks) is deliberately not + # checked here -- those enums change without an SDK release, and its 400 + # message already names the offending field and the options endpoint. + # ------------------------------------------------------------------ + @staticmethod + def _coerce_enum(value: Any, enum_cls: type[_StrEnumT], field: str) -> _StrEnumT: + """Coerce *value* into *enum_cls*, naming the valid values when it fails.""" + if isinstance(value, enum_cls): + return value + try: + return enum_cls(str(value)) + except ValueError as exc: + allowed = ", ".join(repr(member.value) for member in enum_cls) + raise InvalidParameter(f"{field} must be one of {allowed} (got {value!r}).") from exc + + @staticmethod + def _strip_category_discriminator(payload: dict[str, Any], field: str, setter: str) -> dict[str, Any]: + """Drop the category discriminator from a PATCH payload. + + The endpoint answers 400 if the field is present at all -- it reads the + current category itself. Earlier drafts of this API required the field, + so it is removed with a warning rather than forwarded into a failure. + """ + if field in payload: + payload.pop(field) + logger.warning( + "%s is not accepted when updating zone fields and was dropped; use %s() to change the category.", + field, + setter, + ) + return payload + + @staticmethod + def _reject_set_only_fields(payload: Mapping[str, Any]) -> None: + """Reject fields that only ``set_model_category`` can change. + + An existing version's weights are immutable; changing them means + creating a new version. Saying so here is more useful than relaying the + server's rejection. + + ``version_name`` is absent from this list on purpose: the method takes + it as a named argument and resolves it to a ``version_id``, so it never + reaches the payload. + """ + offenders = sorted({"weight_files", "base_model_id"} & set(payload)) + if offenders: + raise InvalidParameter( + f"{', '.join(offenders)} cannot be changed on an existing version. " + f"Use set_model_category(model_category='aigc', ...) to create a new version instead." + ) + + @classmethod + def _validate_set_model_category(cls, category: ModelCategory, payload: Mapping[str, Any]) -> None: + """Check the required-field rules for a zone transition.""" + if category is not ModelCategory.AIGC: + return + missing = sorted( + field + for field in ( + "aigc_type", + "base_model_architecture_type", + "base_model_architecture_subtype", + "version_name", + "weight_files", + "sample_images", + ) + if not payload.get(field) + ) + if missing: + raise InvalidParameter( + f"model_category='aigc' requires non-empty {', '.join(missing)}. " + f"Valid enum values are published by list_aigc_model_options()." + ) + cls._coerce_enum(payload["aigc_type"], AigcModelType, "aigc_type") + version_name = str(payload["version_name"]) + if len(version_name) > OPENAPI_VERSION_NAME_MAX_LENGTH: + raise InvalidParameter( + f"version_name must be at most {OPENAPI_VERSION_NAME_MAX_LENGTH} characters (got {len(version_name)})." + ) + cls._validate_weight_files(payload["weight_files"]) + cls._validate_sample_images(payload["sample_images"]) + + @staticmethod + def _validate_weight_files(weight_files: Any) -> None: + """Check every weight file reference carries a recognised suffix.""" + for entry in weight_files: + path = entry.get("path_in_repo") if isinstance(entry, Mapping) else None + if not path: + raise InvalidParameter("each weight_files entry needs a non-empty 'path_in_repo'.") + if not str(path).lower().endswith(WEIGHT_FILE_SUFFIXES): + raise InvalidParameter( + f"weight file {path!r} does not have a recognised weight suffix. " + f"Supported suffixes: {', '.join(WEIGHT_FILE_SUFFIXES)}." + ) + + @staticmethod + def _validate_sample_images(sample_images: Any) -> None: + """Check sample images carry a URL and no retired ``text`` key.""" + for entry in sample_images: + if not isinstance(entry, Mapping) or not entry.get("url"): + raise InvalidParameter("each sample_images entry needs a non-empty 'url'.") + if "text" in entry: + raise InvalidParameter( + "sample_images does not accept 'text'; use 'prompt' and 'negative_prompt' instead." + ) + + def _resolve_version_id(self, repo_id: str, version_name: str) -> int: + """Look up a version's numeric id by its name. + + The update endpoint addresses versions by numeric id, but callers think + in version names, so the name is resolved here with one detail read. + """ + detail = self.get_model(repo_id) + versions = detail.get("versions") + if not isinstance(versions, list): + versions = [] + names = [] + for version in versions: + if not isinstance(version, Mapping): + continue + name = version.get("version_name") + names.append(str(name)) + if name == version_name and version.get("version_id") is not None: + return int(version["version_id"]) + available = ", ".join(repr(n) for n in names) or "none" + raise InvalidParameter( + f"version_name={version_name!r} not found on {repo_id!r}. Available versions: {available}." + ) + def list_repos( self, repo_type: RepoTypeLike, @@ -1167,12 +1932,11 @@ def list_repos( def delete_repo(self, repo_id: str, repo_type: RepoTypeLike) -> None: """Delete a repository. - .. deprecated:: - Programmatic repository deletion is not currently supported by - the Hub API for security reasons. This method will be restored - in a future release once proper token-scoped authentication is - available. To delete a repository now, use the web console at - https://modelscope.cn. + Needs a token carrying repository deletion permission; an admin-level + token has been verified to work, while ordinary write permission does + not guarantee it. Do not infer the permission from the token's name or + kind. Deletion uses the legacy endpoint -- the current OpenAPI contract + exposes no repository-deletion route. Parameters ---------- @@ -1180,16 +1944,20 @@ def delete_repo(self, repo_id: str, repo_type: RepoTypeLike) -> None: Canonical ``owner/name`` identifier. repo_type : str or RepoType Repository type (``"model"``, ``"dataset"``, etc.). - """ - import warnings - warnings.warn( - "This function is deprecated due to security reasons, " - "and will be recovered in future versions with proper token authentication. " - "Please go to https://modelscope.cn to delete repositories via the web console.", - DeprecationWarning, - stacklevel=2, - ) + Raises + ------ + AuthenticationError or InvalidParameter + When the token lacks deletion permission. The Hub answers business + code ``10010101003`` for models and ``10020301011`` for datasets, + both saying the operation must be performed at + https://modelscope.cn. Retrying with the same token will not help; + use a token with deletion permission or the web console. + + Examples + -------- + >>> api.delete_repo("alice/my-model", "model") + """ rt = self._normalize_repo_type(repo_type) self._parse_repo_id(repo_id) self.legacy.delete_repo(repo_id=repo_id, repo_type=str(rt)) @@ -2060,16 +2828,29 @@ def update_repo_settings( repo_type: RepoTypeLike, **settings: Any, ) -> dict: - """Update repo settings (Studio or Skill). + """Update a repository's general settings. + + Supported for ``model``, ``dataset``, ``studio`` and ``skill``. Only the + keys you pass are changed; for models and datasets an explicit ``None`` + clears metadata fields, while None for visibility/gated_mode leaves + those controls unchanged. Zone membership is not a setting -- use + :meth:`set_model_category` / :meth:`set_dataset_category` for that. Parameters ---------- repo_id : str Canonical ``owner/name`` identifier. repo_type : str or RepoType - Either ``"studio"`` or ``"skill"``. + ``"model"``, ``"dataset"``, ``"studio"`` or ``"skill"``. **settings : Any - Setting key/value pairs forwarded to the backend. + Setting key/value pairs forwarded to the backend. For models and + datasets: ``display_name``, ``license``, ``visibility``, ``description`` + and ``task`` (models only). ``gated_mode`` remains a boolean switch: + True enables application access, False disables it, None leaves it + unchanged. Omitted visibility with either explicit boolean implies + private; explicit public takes precedence and ignores the switch. + High-level visibility accepts the historical integer enum/labels, + not the native OpenAPI 'gated' access-mode string. Returns ------- @@ -2079,7 +2860,8 @@ def update_repo_settings( Raises ------ NotSupportedError - When ``repo_type`` is neither studio nor skill. + When ``repo_type`` has no settings endpoint, or when a setting the + endpoint cannot change is requested (notably ``login_required``). Examples -------- @@ -2089,15 +2871,75 @@ def update_repo_settings( ... visibility="public", ... hardware="GPU-A10", ... ) + >>> api.update_repo_settings( + ... "alice/rna-seq", "dataset", visibility="private", + ... gated_mode=True, + ... ) + >>> api.update_repo_settings("alice/rna-seq", "dataset", gated_mode=False) """ + self._reject_removed_gating_parameters(settings) rt = self._normalize_repo_type(repo_type) owner, name = self._parse_repo_id(repo_id) if rt is RepoType.STUDIO: return self.openapi.update_studio_settings(owner, name, _normalise_studio_fields(settings)) if rt is RepoType.SKILL: return self.openapi.update_skill_settings(owner, name, settings) + if rt is RepoType.DATASET: + payload = self._repo_settings_payload(rt, settings) + data = self.openapi.update_dataset_settings(owner, name, payload) + return dict(data) if isinstance(data, Mapping) else {} + if rt is RepoType.MODEL: + payload = self._repo_settings_payload(rt, settings) + data = self.openapi.update_model_settings(owner, name, payload) + return dict(data) if isinstance(data, Mapping) else {} raise NotSupportedError(f"update_repo_settings is not supported for repo_type={rt.value!r}.") + @classmethod + def _repo_settings_payload(cls, rt: RepoType, settings: Mapping[str, Any]) -> dict[str, Any]: + """Adapt the historical boolean switch and visibility to OpenAPI. + + None leaves the switch/visibility unchanged. True/False with omitted + visibility implies private. Native strings belong to the low-level + OpenAPI payload, not to this high-level boolean parameter. + """ + cls._reject_removed_gating_parameters(settings) + payload = dict(settings) + unsupported = sorted(_UNSETTABLE_FIELDS & set(payload)) + if unsupported: + raise NotSupportedError( + f"{', '.join(unsupported)} cannot be changed through update_repo_settings: " + f"the {rt.value} settings endpoint does not accept it. " + f"It is reported read-only by get_{rt.value}()." + ) + switch = payload.pop("gated_mode", None) + if switch is not None and not isinstance(switch, bool): + raise InvalidParameter("gated_mode must be True, False or None, not an approval-policy string.") + raw_visibility = payload.pop("visibility", None) + visibility = cls._normalize_visibility(raw_visibility) + if visibility is not None: + if visibility not in (int(Visibility.PRIVATE), int(Visibility.PUBLIC)): + raise InvalidParameter( + f"visibility={raw_visibility!r} is not supported by the {rt.value} settings endpoint; " + "there is no equivalent legacy settings endpoint." + ) + wire_visibility = cls._openapi_visibility(rt, visibility) + assert wire_visibility is not None + payload["visibility"] = wire_visibility.value + if switch is not None: + if visibility is None: + visibility = int(Visibility.PRIVATE) + payload["visibility"] = RepoVisibility.PRIVATE.value + if visibility == int(Visibility.PRIVATE): + if switch: + # OpenAPI represents application access as a wire access mode. + payload["visibility"] = RepoVisibility.GATED.value + payload["gated_mode"] = "auto" + else: + payload["visibility"] = RepoVisibility.PRIVATE.value + else: + logger.warning("gated_mode is only effective when visibility is PRIVATE, ignored.") + return payload + # ================================================================== # Secrets # ================================================================== diff --git a/src/modelscope_hub/cli/repo.py b/src/modelscope_hub/cli/repo.py index 7072896..361d5d0 100644 --- a/src/modelscope_hub/cli/repo.py +++ b/src/modelscope_hub/cli/repo.py @@ -8,6 +8,7 @@ import argparse from pathlib import Path +from typing import Any from ..constants import RepoType from ..errors import AlreadyExistsError, is_repo_exists_error @@ -119,7 +120,10 @@ def _add_arguments(p) -> None: def execute(self) -> None: api = make_api(self.args) - extra: dict[str, object] = {} + # ``Any`` rather than ``object``: these come from argparse and are + # forwarded verbatim into ``create_repo``'s keyword parameters, whose + # types vary per repo kind. + extra: dict[str, Any] = {} for key in ("sdk_type", "sdk_version", "base_image", "cover_image", "hardware", "category"): value = getattr(self.args, key, None) if value is not None: diff --git a/src/modelscope_hub/compat/hub_api.py b/src/modelscope_hub/compat/hub_api.py index 5d91634..78334a8 100644 --- a/src/modelscope_hub/compat/hub_api.py +++ b/src/modelscope_hub/compat/hub_api.py @@ -167,6 +167,25 @@ def get_model(self, model_id: str, revision: str | None = None) -> dict: info = self._api.get_repo(model_id, RepoType.MODEL, revision=revision) return _repo_info_to_dict(info) + def get_model_detail( + self, + model_id: str, + *, + endpoint: str | None = None, + token: str | None = None, + ) -> dict: + """Fetch a model's full detail payload, zone fields included. + + Distinct from :meth:`get_model`, which keeps its historical shape: a + ``RepoInfo`` flattened to legacy PascalCase keys. That shape has no room + for the AIGC / science zone fields, so this method returns the server's + snake_case ``ModelDetail`` verbatim instead. + """ + api = self._api + if token or endpoint: + api = HubApi(endpoint=endpoint or self._endpoint, token=token, user_agent={"invoked_by": "compat"}) + return api.get_model(model_id) + def get_model_files( self, model_id: str, @@ -263,6 +282,82 @@ def create_repo( return None raise + def set_model_category( + self, + model_id: str, + *, + token: str | None = None, + endpoint: str | None = None, + **kwargs: Any, + ) -> dict: + """Set or change a model's zone. Wraps :meth:`HubApi.set_model_category`.""" + return self._api_for_write(token, endpoint).set_model_category(model_id, RepoType.MODEL, **kwargs) + + def update_model_category( + self, + model_id: str, + *, + token: str | None = None, + endpoint: str | None = None, + **kwargs: Any, + ) -> dict: + """Update a model's zone fields. Wraps :meth:`HubApi.update_model_category`.""" + return self._api_for_write(token, endpoint).update_model_category(model_id, RepoType.MODEL, **kwargs) + + def set_dataset_category( + self, + dataset_id: str, + *, + token: str | None = None, + endpoint: str | None = None, + **kwargs: Any, + ) -> dict: + """Set or change a dataset's zone. Wraps :meth:`HubApi.set_dataset_category`.""" + return self._api_for_write(token, endpoint).set_dataset_category(dataset_id, RepoType.DATASET, **kwargs) + + def update_dataset_category( + self, + dataset_id: str, + *, + token: str | None = None, + endpoint: str | None = None, + **kwargs: Any, + ) -> dict: + """Update a dataset's science fields. Wraps :meth:`HubApi.update_dataset_category`.""" + return self._api_for_write(token, endpoint).update_dataset_category(dataset_id, RepoType.DATASET, **kwargs) + + def update_dataset( + self, + dataset_id: str, + *, + token: str | None = None, + endpoint: str | None = None, + **kwargs: Any, + ) -> dict: + """Update a dataset's general settings. Wraps :meth:`HubApi.update_repo_settings`.""" + return self._api_for_write(token, endpoint).update_repo_settings(dataset_id, RepoType.DATASET, **kwargs) + + def update_model( + self, + model_id: str, + *, + token: str | None = None, + endpoint: str | None = None, + **kwargs: Any, + ) -> dict: + """Update a model's general settings. Wraps :meth:`HubApi.update_repo_settings`.""" + return self._api_for_write(token, endpoint).update_repo_settings(model_id, RepoType.MODEL, **kwargs) + + def _api_for_write(self, token: str | None, endpoint: str | None) -> HubApi: + """Return the façade to use, honouring per-call token/endpoint overrides.""" + if token or endpoint: + return HubApi( + endpoint=endpoint or self._endpoint, + token=token, + user_agent={"invoked_by": "compat"}, + ) + return self._api + def create_model(self, model_id: str, **kwargs: Any) -> str: """Create a model repo (legacy signature). @@ -287,6 +382,7 @@ def create_model(self, model_id: str, **kwargs: Any) -> str: def _create_aigc_model(self, model_id: str, aigc_model: Any, kwargs: dict[str, Any]) -> str: """Create an AIGC model without changing the plain-model code path.""" + HubApi._reject_removed_gating_parameters(kwargs) token = kwargs.pop("token", None) endpoint = kwargs.pop("endpoint", None) visibility = kwargs.pop("visibility", None) @@ -918,6 +1014,9 @@ def create_dataset( visibility=visibility, license=license, chinese_name=chinese_name, + # Forwarded explicitly: this argument used to be accepted and then + # dropped, so a caller's description never reached the Hub. + description=description, ) ep = endpoint or self._endpoint or api._config.endpoint return f"{ep}/datasets/{namespace}/{dataset_name}" @@ -956,6 +1055,24 @@ def get_dataset( api = HubApi(endpoint=endpoint or self._endpoint, token=token, user_agent={"invoked_by": "compat"}) return api.get_repo(dataset_id, RepoType.DATASET, revision=revision) + def get_dataset_detail( + self, + dataset_id: str, + *, + endpoint: str | None = None, + token: str | None = None, + ) -> dict: + """Fetch a dataset's full detail payload, science fields included. + + Distinct from :meth:`get_dataset`, which keeps its historical shape (a + ``RepoInfo``) and has no room for the science zone fields. Returns the + server's snake_case ``DatasetDetail`` verbatim. + """ + api = self._api + if token or endpoint: + api = HubApi(endpoint=endpoint or self._endpoint, token=token, user_agent={"invoked_by": "compat"}) + return api.get_dataset(dataset_id) + def delete_model( self, model_id: str, @@ -974,10 +1091,17 @@ def delete_dataset( endpoint: str | None = None, token: str | None = None, ) -> None: - """Delete a dataset repository.""" + """Delete a dataset repository. + + Needs a token carrying deletion permission; see + :meth:`HubApi.delete_repo`. The warning is kept so callers that filter + on it keep behaving as before, but its wording now describes the actual + constraint rather than claiming the method is going away. + """ warnings.warn( - "This function is deprecated due to security reasons, " - "and will be recovered in future versions with proper token authentication.", + "delete_dataset requires a token with deletion permission. " + "Ordinary write permission alone may not authorise repository deletion; " + "use an admin-level token or delete via https://modelscope.cn.", DeprecationWarning, stacklevel=2, ) diff --git a/src/modelscope_hub/constants.py b/src/modelscope_hub/constants.py index c4ca763..b494d67 100644 --- a/src/modelscope_hub/constants.py +++ b/src/modelscope_hub/constants.py @@ -139,6 +139,97 @@ def parse(cls, value: object) -> StudioVisibility | None: return None +class RepoVisibility(StrEnum): + """Visibility values the OpenAPI model/dataset endpoints accept. + + Deliberately separate from :class:`Visibility`: the ``/openapi/v1`` model and + dataset endpoints take a string enum whose third state (``gated``) the 1/3/5 + integer encoding cannot express, while that encoding's ``INTERNAL`` has no + string counterpart. Neither enum is a relabelling of the other, so they are + mapped explicitly at the call site rather than merged. + + * ``public`` -- visible to everyone. + * ``private`` -- visible only to the owner and collaborators. + * ``gated`` -- discoverable, but downloads require an approved application. + """ + + PUBLIC = "public" + PRIVATE = "private" + GATED = "gated" + + @classmethod + def parse(cls, value: object) -> RepoVisibility | None: + """Return the matching member, or ``None`` when *value* is not one. + + Mirrors :meth:`StudioVisibility.parse`: returning ``None`` lets callers + fall back to the integer :class:`Visibility` encoding for inputs this + enum does not own. + """ + if isinstance(value, cls): + return value + if isinstance(value, str): + try: + return cls(value.strip().lower()) + except ValueError: + return None + return None + + +class GatedMode(StrEnum): + """How download applications against a gated repository are approved. + + Only meaningful when visibility is ``gated``; the endpoints reject the field + for ``public``/``private`` repositories. + """ + + AUTO = "auto" + MANUAL = "manual" + + @classmethod + def parse(cls, value: object) -> GatedMode | None: + """Return the matching member, or ``None`` when *value* is not one.""" + if isinstance(value, cls): + return value + if isinstance(value, str): + try: + return cls(value.strip().lower()) + except ValueError: + return None + return None + + +class ModelCategory(StrEnum): + """Which curated zone a model belongs to. + + A freshly created model is ``base``; ``PUT /models/{owner}/{name}/category`` + moves it into a zone, and passing ``base`` moves it back out and clears the + zone-specific fields. + """ + + BASE = "base" + AIGC = "aigc" + SCIENCE = "science" + + +class DatasetCategory(StrEnum): + """Which curated zone a dataset belongs to. + + Note the general value is ``base`` -- matching the model enum and the wire + protocol -- not ``basic``. + """ + + BASE = "base" + SCIENCE = "science" + + +class AigcModelType(StrEnum): + """Sub-type of an AIGC model. Values are case-sensitive on the wire.""" + + CHECKPOINT = "Checkpoint" + LORA = "LoRA" + VAE = "VAE" + + class TokenScope(StrEnum): """Permission tiers a ModelScope API token can be issued with. @@ -197,6 +288,109 @@ class License(StrEnum): REPO_TYPE_SUPPORT: list[str] = [REPO_TYPE_MODEL, REPO_TYPE_DATASET, REPO_TYPE_STUDIO] DEFAULT_DATASET_REVISION: str = "master" +# --------------------------------------------------------------------------- +# OpenAPI model/dataset contract limits. +# +# These mirror constraints the ``/openapi/v1`` specification declares. They are +# kept here so a request that the server would certainly reject can be rejected +# locally with a message that names the fix, rather than costing a round trip +# for an opaque 400. +# --------------------------------------------------------------------------- + +# Suffixes accepted for an AIGC version's weight files (matched case-insensitively). +WEIGHT_FILE_SUFFIXES: tuple[str, ...] = ( + ".safetensors", + ".ckpt", + ".pt", + ".pth", + ".bin", + ".gguf", + ".ggml", + ".h5", + ".hdf5", + ".npz", + ".msgpack", + ".pdparams", + ".params", + ".onnx", +) + +# ``DELETE /{models,datasets}/{owner}/{name}/files`` accepts 1..100 paths. +OPENAPI_DELETE_FILES_MAX_PATHS: int = 100 + +# Largest ``page_size`` the OpenAPI list endpoints accept. Advisory: the value is +# forwarded as given so a server-side change does not need an SDK release. +OPENAPI_PAGE_SIZE_MAX: int = 50 + +# Longest ``version_name`` accepted when creating an AIGC version. +OPENAPI_VERSION_NAME_MAX_LENGTH: int = 20 + +# ``POST /models`` constrains ``license`` to a closed set, storing the value +# verbatim and case-sensitively. Anything outside it answers 400, so a caller +# supplying another identifier is routed to the legacy endpoint instead, which +# accepts free-form values. ``POST /datasets`` declares no such restriction. +OPENAPI_MODEL_LICENSES: frozenset[str] = frozenset( + { + "", + "apache-2.0", + "Apache License 2.0", + "gpl-2.0", + "GPL-2.0", + "lgpl-3.0", + "LGPL-3.0", + "mit", + "MIT License", + "lgpl-2.1", + "LGPL-2.1", + "gpl-3.0", + "GPL-3.0", + "afl-3.0", + "AFL-3.0", + "ecl-2.0", + "ECL-2.0", + "cc-by-4.0", + "CC-BY-4.0", + "creativeml-openrail-m", + "cc-by-nc-nd", + "CC-BY-NC-ND", + "agpl-3.0", + "cc-by-nc-4.0", + "CC-BY-NC-4.0", + "cc-by-nc-sa-4.0", + "CC-BY-NC-SA-4.0", + "odc-by-1.0", + "ODC-BY-1.0", + "cc-by-sa-4.0", + "CC-BY-SA-4.0", + "cc0-1.0", + "CC0-1.0", + "bsd-3-clause", + "BSD-3-Clause", + "other", + } +) + +# Every field ``POST /models`` and ``POST /datasets`` accept. The endpoints answer +# 400 for anything else, so a caller passing an extra field is routed to the +# legacy endpoint, which forwards unknown keys verbatim. +OPENAPI_CREATE_MODEL_FIELDS: frozenset[str] = frozenset( + { + "owner", + "repo_name", + "display_name", + "license", + "license_name", + "license_link", + "visibility", + "gated_mode", + "description", + "task", + } +) + +# Same list minus ``task``, which the dataset schema does not declare. +OPENAPI_CREATE_DATASET_FIELDS: frozenset[str] = OPENAPI_CREATE_MODEL_FIELDS - {"task"} + # --------------------------------------------------------------------------- # Legacy modelscope domain / endpoint / filesystem constants. # @@ -605,6 +799,21 @@ def _env_register( use. This must be at least as large as the worker count to avoid that churn. """ +OPTIONS_CACHE_TTL_SECONDS: int = _env_int( + "MODELSCOPE_OPTIONS_CACHE_TTL", + 300, + "Seconds to cache the model/dataset enum option endpoints (0 disables)", + "Network", + allow_zero=True, +) +"""Lifetime of the in-process cache for the ``*/options`` endpoints. + +Those endpoints publish product configuration (AIGC architectures, science +subject areas, tasks, licenses) that changes on a release cadence, not per +request, and they need no credentials. Setting a category therefore does not +have to re-fetch the enum every call. Set to ``0`` to disable caching. +""" + REPO_TREE_PAGE_MAX_ATTEMPTS: int = _env_int( "MODELSCOPE_REPO_TREE_PAGE_MAX_ATTEMPTS", 4, diff --git a/src/modelscope_hub/types.py b/src/modelscope_hub/types.py index c6b90ac..bbdb3ca 100644 --- a/src/modelscope_hub/types.py +++ b/src/modelscope_hub/types.py @@ -417,6 +417,13 @@ class RepoInfo(_FromDictMixin): hardware: str | None = None mcp_support: bool | None = None runtime: dict[str, Any] | None = None + # Zone membership. Reported by the model/dataset detail endpoints; ``None`` + # for repo kinds that have no zones (studio / skill / mcp) and for payloads + # predating the field. + model_category: str | None = None + dataset_category: str | None = None + # Approval mode of a gated repository, returned only when ``gated`` is true. + gated_mode: str | None = None def __post_init__(self) -> None: if isinstance(self.repo_type, str): @@ -451,6 +458,9 @@ def to_dict(self) -> dict: "hardware", "mcp_support", "runtime", + "model_category", + "dataset_category", + "gated_mode", } result = {} for f in fields(self): # type: ignore[arg-type] @@ -730,7 +740,275 @@ class TokenSignPayload(TypedDict): signature: str +# --------------------------------------------------------------------------- +# Model / dataset zone structures +# +# Shapes shared by the category endpoints and the enum option endpoints. Field +# names follow the OpenAPI schemas exactly: the server rejects unknown keys, so +# a convenience alias here would turn into a 400 at the call site. +# --------------------------------------------------------------------------- +class SampleImage(TypedDict, total=False): + """A sample image attached to an AIGC model version. + + ``url`` is required. The schema accepts only these three keys -- notably + there is no ``text`` field. + """ + + url: str + prompt: str + negative_prompt: str + + +class WeightFileReference(TypedDict): + """Reference to a weight file already uploaded to the repository. + + Only the repo-relative path is sent; the server resolves size and digest + itself. The suffix must be one of :data:`WEIGHT_FILE_SUFFIXES`. + """ + + path_in_repo: str + + +class AigcContentCategories(TypedDict, total=False): + """Three-level content classification of an AIGC model.""" + + style: str + function: str + element: str + + +class ModelOptionItem(TypedDict): + """One selectable value returned by an ``*/options`` endpoint. + + Submit ``id``; ``label`` is for display only. ``type`` is self-describing + (``task``, ``license``, ``base_architecture``, ``specialization``, ...). + """ + + type: str + id: str + label: str + + +class BaseArchitectureOption(TypedDict): + """A first-level AIGC base architecture and its second-level subtypes.""" + + type: str + id: str + label: str + subtypes: list[ModelOptionItem] + + +class ScienceSubjectOption(TypedDict): + """A first-level science subject area and its specializations.""" + + type: str + id: str + label: str + specializations: list[ModelOptionItem] + + +# --------------------------------------------------------------------------- +# Model / dataset request payloads +# +# Each mirrors one OpenAPI request schema exactly. Fields the schema omits are +# omitted here too, because the endpoints reject unknown keys with a 400 -- +# notably ``base_model_id`` (the base model is derived from the architecture +# subtype), ``login_required`` (read-only), and the category discriminator on +# the PATCH payloads (the server reads the current category from its own +# database and rejects the field). +# --------------------------------------------------------------------------- +class CreateModelPayload(TypedDict, total=False): + """Payload for ``POST /models``. ``owner`` and ``repo_name`` are required. + + Carries general metadata only; a newly created model is ``base``. Zone + fields are set afterwards via ``PUT /models/{owner}/{repo_name}/category``. + """ + + owner: str + repo_name: str + display_name: str + license: str + license_name: str + license_link: str + visibility: str + gated_mode: str + description: str + # The schema declares ``string | null``: an explicit null means "not set". + task: str | None + + +class CreateDatasetPayload(TypedDict, total=False): + """Payload for ``POST /datasets``. ``owner`` and ``repo_name`` are required. + + Mirrors :class:`CreateModelPayload` without ``task``, which the dataset + schema does not declare. + """ + + owner: str + repo_name: str + display_name: str + license: str + license_name: str + license_link: str + visibility: str + gated_mode: str + description: str + + +class SetModelCategoryPayload(TypedDict, total=False): + """Payload for ``PUT /models/{owner}/{repo_name}/category``. + + ``model_category`` is required and names the *target* zone; which other + fields are required follows from it, not from the model's current zone: + + * ``base`` -- nothing else; every zone field is cleared. + * ``aigc`` -- ``aigc_type``, both architecture levels, ``version_name``, and + non-empty ``weight_files`` and ``sample_images``. Creates a new version. + * ``science`` -- ``primary_subject_area`` and ``secondary_specialization``; + ``cover_image`` is optional. + + Passing a field that does not apply to the target zone returns 400. + """ + + model_category: str + # AIGC + aigc_type: str + base_model_architecture_type: str + base_model_architecture_subtype: str + content_categories: AigcContentCategories + version_name: str + version_description: str + trigger_words: list[str] + sample_images: list[SampleImage] + weight_files: list[WeightFileReference] + # Science + primary_subject_area: str + secondary_specialization: str + cover_image: str + + +class UpdateModelCategoryPayload(TypedDict, total=False): + """Payload for ``PATCH /models/{owner}/{repo_name}/category``. + + Updates zone metadata in place. The server determines the zone itself, so + ``model_category`` is not accepted; use ``PUT`` to change zones. + + For AIGC, ``version_id`` (the numeric id from a detail response's + ``versions[]``) is required to update ``trigger_words``, + ``version_description`` or ``sample_images``. Science updates must not send + it. An existing version's weights are immutable: ``weight_files``, + ``version_name`` and ``base_model_id`` are rejected -- create a new version + with ``PUT`` instead. Omitted fields are unchanged; explicit nulls clear. + """ + + version_id: int + # AIGC + aigc_type: str | None + base_model_architecture_type: str | None + base_model_architecture_subtype: str | None + content_categories: AigcContentCategories | None + trigger_words: list[str] | None + version_description: str | None + sample_images: list[SampleImage] + # Science + primary_subject_area: str | None + secondary_specialization: str | None + cover_image: str | None + + +class SetDatasetCategoryPayload(TypedDict, total=False): + """Payload for ``PUT /datasets/{owner}/{repo_name}/category``. + + ``dataset_category`` is required and is ``base`` or ``science`` -- the + general value is ``base``, not ``basic``. ``science`` requires a matching + subject area and specialization; ``base`` clears the science fields. + """ + + dataset_category: str + primary_subject_area: str | None + secondary_specialization: str | None + cover_image: str | None + + +class UpdateDatasetCategoryPayload(TypedDict, total=False): + """Payload for ``PATCH /datasets/{owner}/{repo_name}/category``. + + ``dataset_category`` is not accepted -- the server reads the current one and + returns 400 if the field is present; use ``PUT`` to change zones. + """ + + primary_subject_area: str | None + secondary_specialization: str | None + cover_image: str | None + + +class UpdateDatasetSettingsPayload(TypedDict, total=False): + """Payload for ``PATCH /datasets/{owner}/{repo_name}/settings``. + + General settings only; zone fields are rejected. ``login_required`` is + deliberately absent -- the schema does not accept it, the field is only ever + reported back by the detail endpoint. + + Omitted fields are unchanged; explicit nulls clear. ``gated_mode`` is valid + only alongside ``visibility="gated"`` (or on a repo that is already gated). + """ + + display_name: str | None + license: str | None + visibility: str | None + gated_mode: str + description: str | None + + +class UpdateModelSettingsPayload(TypedDict, total=False): + """Payload for ``PATCH /models/{owner}/{repo_name}/settings``. + + General settings only; zone fields are rejected. Mirrors + :class:`UpdateDatasetSettingsPayload` plus ``task``. + + Omitted fields are unchanged; explicit nulls clear. ``gated_mode`` is valid + only alongside ``visibility="gated"`` (or on a repo that is already gated). + """ + + task: str | None + display_name: str | None + license: str | None + visibility: str | None + gated_mode: str + description: str | None + + +class DeleteFilesPayload(TypedDict, total=False): + """Payload for ``DELETE /{models,datasets}/{owner}/{repo_name}/files``. + + ``file_paths`` is required: 1 to 100 distinct repo-relative file paths. No + directories and no traversal. The whole request is one commit and is + all-or-nothing -- a single unresolvable path fails the lot. + + ``revision`` must name a branch, not a tag. + """ + + file_paths: list[str] + revision: str + commit_message: str + + __all__ = [ + "AigcContentCategories", + "BaseArchitectureOption", + "CreateDatasetPayload", + "CreateModelPayload", + "DeleteFilesPayload", + "ModelOptionItem", + "SampleImage", + "ScienceSubjectOption", + "SetDatasetCategoryPayload", + "SetModelCategoryPayload", + "UpdateDatasetCategoryPayload", + "UpdateDatasetSettingsPayload", + "UpdateModelCategoryPayload", + "UpdateModelSettingsPayload", + "WeightFileReference", "DownloadRetryPolicy", "DownloadFileResult", "DownloadReport", diff --git a/src/modelscope_hub/version.py b/src/modelscope_hub/version.py index 48b097a..b22f9da 100644 --- a/src/modelscope_hub/version.py +++ b/src/modelscope_hub/version.py @@ -1,3 +1,3 @@ """Version information for modelscope_hub.""" -__version__ = "0.5.1+main" +__version__ = "0.6.0+main" diff --git a/tests/cli/test_repo.py b/tests/cli/test_repo.py index e4400a1..924a333 100644 --- a/tests/cli/test_repo.py +++ b/tests/cli/test_repo.py @@ -53,6 +53,16 @@ def test_invalid_visibility_rejected(self, parser): with pytest.raises(SystemExit): parser.parse_args(["create", "o/r", "--repo-type", "model", "--visibility", "secret"]) + @pytest.mark.parametrize("mode", ["auto", "manual"]) + def test_removed_approval_mode_flag(self, parser, mode): + with pytest.raises(SystemExit): + parser.parse_args(["create", "o/r", "--repo-type", "model", "--gated", "--approval-mode", mode]) + + def test_old_gated_flag_remains_boolean(self, parser): + args = parser.parse_args(["create", "o/r", "--repo-type", "model", "--gated"]) + assert args.gated is True + assert not hasattr(args, "approval_mode") + def test_license_flag(self, parser): args = parser.parse_args(["create", "o/r", "--repo-type", "model", "--license", "apache-2.0"]) assert args.license == "apache-2.0" @@ -339,6 +349,15 @@ def test_create_model(self, parser, mock_api, capsys): out = capsys.readouterr().out assert "Created" in out + @pytest.mark.parametrize("flag, expected", [("--gated", True), ("--no-gated", False)]) + def test_create_with_historical_boolean_flag(self, parser, mock_api, flag, expected): + args = parser.parse_args(["create", "owner/model", "--repo-type", "model", flag]) + with patch("modelscope_hub.cli.repo.make_api", return_value=mock_api): + CreateCommand(args).execute() + kwargs = mock_api.create_repo.call_args.kwargs + assert kwargs["gated_mode"] is expected + assert "gated" not in kwargs and "approval_mode" not in kwargs + def test_create_studio_with_extras(self, parser, mock_api, capsys): args = parser.parse_args( [ diff --git a/tests/data/openapi.json b/tests/data/openapi.json index aaed4a3..4ee1a83 100644 --- a/tests/data/openapi.json +++ b/tests/data/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "ModelScope OpenAPI", "description": "魔搭社区 OpenAPI 文档", - "version": "1.1.0+master.20260813T030041Z", + "version": "1.1.0+master.20261022T105704Z", "termsOfService": "https://www.modelscope.cn/protocol/%E8%81%94%E7%B3%BB%E6%88%91%E4%BB%AC", "contact": { "name": "modelscope", @@ -50,6 +50,10 @@ "name": "Skills", "description": "技能相关接口" }, + { + "name": "AgentHub", + "description": "AgentHub 相关接口" + }, { "name": "Files", "description": "文件上传相关接口" @@ -69,6 +73,10 @@ { "name": "Galleries", "description": "Gallery 相关接口" + }, + { + "name": "Agent Playground", + "description": "实验场(Agent Playground)相关接口(beta版本)" } ], "externalDocs": { @@ -294,13 +302,63 @@ "bearerAuth": [] } ] + }, + "post": { + "operationId": "createModel", + "summary": "创建通用模型仓库", + "description": "创建一个新的通用模型仓库。默认 `model_category` 为 `base`,即未进入任何专区的普通模型。\n\n如需创建 AIGC 模型或科学模型,请先调用本接口创建仓库,上传权重等文件后,再调用 `PUT /models/{owner}/{repo_name}/category` 设置模型类型及专区业务字段。\n\n使用用户访问令牌(Bearer Token)认证。只能在自己的命名空间或拥有写入/管理员权限的组织下创建。当前 OAuth 仅读取仓库的授权不支持创建。\n\n省略 license 时默认 apache-2.0;license=\"other\" 时必须提供 license_name,省略 license_link 时自动创建空白 LICENSE.md。创建时仅 visibility=\"gated\" 可传 gated_mode:auto 自动审批,manual 人工审批;省略或空字符串默认 auto。null、布尔值、数字及其他非法值返回 400。visibility 为 public/private(含省略后默认 public)时禁止传 gated_mode。请求仅接受文档列出的字段,未知字段返回 400。\n\n重复仓库返回 HTTP 409(DuplicateEntity);权限或私有仓库配额不足返回 HTTP 403。创建成功返回 HTTP 201,所有 JSON 响应包含 request_id。\n\n本接口仅接收通用元数据字段,不接受 `aigc_type`、`base_model_architecture_type`、`primary_subject_area` 等专区字段。", + "tags": [ + "Models" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateModelRequest" + }, + "example": { + "owner": "alice", + "repo_name": "my-model", + "visibility": "gated", + "gated_mode": "manual", + "task": "text-generation" + } + } + } + }, + "responses": { + "201": { + "$ref": "#/components/responses/create-model-success" + }, + "400": { + "$ref": "#/components/responses/bad-request" + }, + "401": { + "$ref": "#/components/responses/unauthorized" + }, + "403": { + "$ref": "#/components/responses/forbidden" + }, + "409": { + "$ref": "#/components/responses/conflict" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] } }, "/models/{owner}/{repo_name}": { "get": { "operationId": "getModel", "summary": "获取模型详情", - "description": "根据 owner/repo_name 获取模型详情", + "description": "根据 owner/repo_name 获取模型详情。model_category=base 时无专区字段;science 返回学科与封面;aigc 返回架构、内容类别及 versions。每个版本含数值 version_id、固定 Git tag 的 weight_files 元数据和 sample_images;AIGC 不返回 cover_image。所有响应均有 request_id。", "tags": [ "Models" ], @@ -336,6 +394,228 @@ ] } }, + "/models/{owner}/{repo_name}/category": { + "put": { + "operationId": "setModelCategory", + "summary": "设置/变更模型类型", + "description": "设置或变更模型的专区类型(`base` / `aigc` / `science`)。\n\n本接口是 PUT 类型转换;PATCH /category 仅局部更新当前类型,不能转换类型。必填项由目标 model_category 决定,与来源类型无关。\n\n| 目标类型 | 必填字段(除 model_category 外) | 可选字段 | 不适用字段 |\n| --- | --- | --- | --- |\n| base | 无 | 无 | AIGC 和 Science 专区字段 |\n| aigc | aigc_type、base_model_architecture_type、base_model_architecture_subtype、version_name、非空 weight_files、非空 sample_images | version_description、trigger_words、content_categories | version_id、cover_image、Science 学科字段 |\n| science | primary_subject_area、secondary_specialization | cover_image | version_id、version_name、weight_files、sample_images 等 AIGC 字段 |\n\n- 类型转换不修改 README.md 内容或 YAML 头;模型类型和专区元数据保存在数据库,AIGC 权重配置仍通过 configuration.json 管理\n- 转换为 AIGC 时创建新版本,传 version_name,不传用于更新已有版本的 version_id\n- 转换为 Science 时不创建 AIGC 版本/tag;无需 version_id,详情不返回 versions\n- 传入 `base` 时,只需提供 `model_category`,所有专区业务字段将被清除(回退为通用模型)\n- 目标为 aigc(包括创建新 tag)时 weight_files 和 sample_images 均必填且非空\n- 基础模型由架构子类型确定\n- AIGC 图片使用 sample_images(url 必填、prompt 和 negative_prompt 选填),cover_image 仅适用于科学专区\n- 需要仓库维护者权限;仅有读取权限的 OAuth scope 不允许修改\n- 科学分类和 AIGC 架构的实际枚举以 options 接口返回的产品数据为准\n- 传入非目标类型的字段将返回 400\n- 必填字段缺失时返回 400,`message` 一次性列出全部缺失字段名,并指引查询对应 options 接口", + "tags": [ + "Models" + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" + }, + { + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetModelCategoryRequest" + }, + "examples": { + "toBase": { + "summary": "转换为普通模型", + "value": { + "model_category": "base" + } + }, + "toAigc": { + "summary": "转换为 AIGC 并创建新版本", + "value": { + "model_category": "aigc", + "aigc_type": "Checkpoint", + "base_model_architecture_type": "Z_IMAGE_TURBO", + "base_model_architecture_subtype": "Z_IMAGE_TURBO", + "version_name": "v1.0", + "weight_files": [ + { + "path_in_repo": "model.safetensors" + } + ], + "sample_images": [ + { + "url": "https://modelscope.oss-cn-beijing.aliyuncs.com/resource/modelscope_logo.png", + "prompt": "清晰的山间风景", + "negative_prompt": "模糊、低清晰度" + } + ] + } + }, + "toScience": { + "summary": "转换为 Science,学科必填、封面选填", + "value": { + "model_category": "science", + "primary_subject_area": "life-sciences", + "secondary_specialization": "medicine" + } + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/set-model-category-success" + }, + "400": { + "$ref": "#/components/responses/bad-request" + }, + "401": { + "$ref": "#/components/responses/unauthorized" + }, + "403": { + "$ref": "#/components/responses/forbidden" + }, + "404": { + "$ref": "#/components/responses/not-found" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "operationId": "updateModelCategory", + "summary": "更新模型专区与已有 tag 元数据", + "description": "Science 更新 primary_subject_area、secondary_specialization、cover_image 不需要 version_id,且不应传 AIGC 版本字段;Science 详情不返回 versions。仅更新当前模型专区或已有 AIGC tag 的元数据;类型由服务端从数据库读取当前仓库确定;不接受 model_category,传入返回 400,类型转换使用 PUT /category。更新 tag 描述、触发词或样例图时,必须提供 GET 详情 versions[].version_id 的数值 ID。省略不变,显式空值清空,但必填字段和非空样例图约束仍需满足。已有 tag 的权重不可修改:weight_files、version_name 和 base_model_id 不接受,权重变化请 PUT /category 创建新 tag。AIGC 不接受 cover_image,样例图使用 sample_images。不适用于数据库当前类型的字段返回 400,message 列出当前类型及所有不适用字段;显式 null 也按传入字段校验。所有响应均有 request_id。此接口不修改 README.md 或 YAML 头。", + "tags": [ + "Models" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetModelCategoryResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/bad-request" + }, + "401": { + "$ref": "#/components/responses/unauthorized" + }, + "403": { + "$ref": "#/components/responses/forbidden" + }, + "404": { + "$ref": "#/components/responses/not-found" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" + }, + { + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateModelCategoryRequest" + }, + "examples": { + "aigcVersion": { + "summary": "更新 AIGC 版本字段,需要 version_id", + "value": { + "version_id": 1070760, + "version_description": null, + "trigger_words": [] + } + }, + "scienceCover": { + "summary": "更新 Science 封面,无需 version_id", + "value": { + "cover_image": "https://modelscope.oss-cn-beijing.aliyuncs.com/resource/modelscope_banner.png" + } + }, + "scienceSubjects": { + "summary": "更新 Science 学科,无需 version_id", + "value": { + "primary_subject_area": "life-sciences", + "secondary_specialization": "medicine" + } + } + } + } + } + } + } + }, + "/model/aigc/options": { + "get": { + "operationId": "listAigcModelOptions", + "summary": "查询 AIGC 模型配置项枚举", + "description": "返回设置 AIGC 模型类型时可用的配置项枚举:aigc_types(模型子类型)、base_architectures(基础模型架构一级/二级级联)、content_styles / content_functions / content_elements(内容类别三级联动)。设置或更新 AIGC 模型的枚举字段取值以本接口返回的 id 为准。 所有层级的选项使用 label 作为展示名称,提交参数使用 id。 基础架构涵盖 IMAGE、VIDEO、AUDIO,base_architectures 为一级架构,subtypes 为二级架构;每项均包含 type、id、label。", + "tags": [ + "Models" + ], + "responses": { + "200": { + "$ref": "#/components/responses/aigc-model-options-success" + }, + "429": { + "$ref": "#/components/responses/too-many-requests" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" + }, + "503": { + "$ref": "#/components/responses/service-unavailable" + } + }, + "security": [] + } + }, + "/model/science/options": { + "get": { + "operationId": "listScienceModelOptions", + "summary": "查询科学模型配置项枚举", + "description": "返回设置科学模型类型时可用的学科分类树:subject_areas(一级学科)及其下属 specializations(二级专业方向)。primary_subject_area / secondary_specialization 的取值以本接口返回的 id 为准。 所有层级的选项使用 label 作为展示名称,提交参数使用 id。", + "tags": [ + "Models" + ], + "responses": { + "200": { + "$ref": "#/components/responses/science-model-options-success" + }, + "429": { + "$ref": "#/components/responses/too-many-requests" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" + }, + "503": { + "$ref": "#/components/responses/service-unavailable" + } + }, + "security": [] + } + }, "/datasets": { "get": { "operationId": "listDatasets", @@ -468,13 +748,70 @@ "bearerAuth": [] } ] + }, + "post": { + "operationId": "createDataset", + "summary": "创建通用数据集仓库", + "description": "创建通用数据集仓库,默认 dataset_category=base。只接受通用元数据;科学字段通过 PUT /category 设置。license=other 时需要 license_name;省略 license_link 自动创建 LICENSE.md。创建时仅 visibility=\"gated\" 可传 gated_mode:auto 自动审批,manual 人工审批;省略或空字符串默认 auto。null、布尔值、数字及其他非法值返回 400。visibility 为 public/private(含省略后默认 public)时禁止传 gated_mode。请求仅接受文档列出的字段,未知字段返回 400。成功返回 201,所有响应均有 request_id。", + "tags": [ + "Datasets" + ], + "responses": { + "201": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetWriteResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/bad-request" + }, + "401": { + "$ref": "#/components/responses/unauthorized" + }, + "403": { + "$ref": "#/components/responses/forbidden" + }, + "404": { + "$ref": "#/components/responses/not-found" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateDatasetRequest" + }, + "example": { + "owner": "alice", + "repo_name": "my-dataset", + "visibility": "gated", + "gated_mode": "manual", + "license": "apache-2.0" + } + } + } + } } }, "/datasets/{owner}/{repo_name}": { "get": { "operationId": "getDataset", "summary": "获取数据集详情", - "description": "根据 owner/repo_name 获取数据集详情", + "description": "根据 owner/repo_name 获取数据集详情。dataset_category=base 时无科学字段;science 返回 primary_subject_area、secondary_specialization 和 cover_image。通用字段包含 visibility 和 login_required。所有响应均有 request_id。", "tags": [ "Datasets" ], @@ -1184,7 +1521,7 @@ "get": { "operationId": "listStudioSecrets", "summary": "获取创空间密文变量列表", - "description": "获取指定 Studio 的所有密文变量 key 列表,不返回 value", + "description": "获取指定 Studio 的所有密文变量 key 列表,不返回 value;创建或更新时填写的 description 会一并返回", "tags": [ "Studios" ], @@ -1232,7 +1569,7 @@ "post": { "operationId": "addStudioSecret", "summary": "添加创空间密文变量", - "description": "为指定的 Studio 添加一个密文变量,value 不会公开展示", + "description": "为指定的 Studio 添加一个密文变量,value 不会公开展示;可通过可选的 description 记录该变量的用途(最长 512 字符),描述含敏感内容时返回 400", "tags": [ "Studios" ], @@ -1287,7 +1624,7 @@ "put": { "operationId": "updateStudioSecret", "summary": "更新创空间密文变量", - "description": "更新指定 Studio 的一个密文变量值", + "description": "更新指定 Studio 的一个密文变量值,并可同时更新可选的 description(最长 512 字符);未提供 description 时保留原有描述,描述含敏感内容时返回 400", "tags": [ "Studios" ], @@ -1393,7 +1730,7 @@ "get": { "operationId": "listStudioVariables", "summary": "获取创空间明文变量列表", - "description": "获取指定 Studio 的所有明文变量的 key 和 value(与密文变量不同,明文变量的值公开可见)", + "description": "获取指定 Studio 的所有明文变量的 key、value 和可选的 description(与密文变量不同,明文变量的值公开可见)", "tags": [ "Studios" ], @@ -1431,7 +1768,7 @@ "post": { "operationId": "addStudioVariable", "summary": "添加创空间明文变量", - "description": "为指定的 Studio 添加一个明文变量(key 和 value 都公开可见,敏感信息请使用密文变量接口)", + "description": "为指定的 Studio 添加一个明文变量(key 和 value 都公开可见,敏感信息请使用密文变量接口);可通过可选的 description 记录该变量的用途(最长 512 字符),描述含敏感内容时返回 400", "tags": [ "Studios" ], @@ -1485,7 +1822,7 @@ "put": { "operationId": "updateStudioVariable", "summary": "更新创空间明文变量", - "description": "更新指定 Studio 的一个明文变量值", + "description": "更新指定 Studio 的一个明文变量值,并可同时更新可选的 description(最长 512 字符);未提供 description 时保留原有描述,描述含敏感内容时返回 400", "tags": [ "Studios" ], @@ -2031,11 +2368,242 @@ ] } }, - "/files/upload": { - "post": { - "operationId": "uploadFile", - "summary": "上传文件", - "description": "通用文件上传接口(最大 5MB),返回文件 ID。该 ID 可作为入参传给其他接口,用于引用已上传的文件。", + "/agents": { + "get": { + "operationId": "listAgents", + "summary": "获取 Agent 列表", + "description": "返回 ModelScope Agent 广场的 Agent 列表,支持关键词搜索与多维度筛选。\n\n默认只返回已发布/预发布的 Agent;携带 Token 访问时,同时返回当前用户及其所属组织下的私有 Agent。\n\n分页总量(`page_number` × `page_size`)不得超过 2400,超出返回 `QuotaLimitExceed`。\n", + "tags": [ + "AgentHub" + ], + "parameters": [ + { + "name": "search", + "in": "query", + "description": "关键词搜索,支持 Agent 名称、描述等", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "filter.framework", + "in": "query", + "description": "按框架类型筛选", + "required": false, + "schema": { + "type": "string" + }, + "examples": { + "ms-agent": { + "value": "ms-agent", + "summary": "ModelScope Agent 框架" + }, + "qwenpaw": { + "value": "qwenpaw", + "summary": "QwenPaw 框架" + } + } + }, + { + "name": "filter.category", + "in": "query", + "description": "按分类筛选(对应 Agent 的 categories)", + "required": false, + "schema": { + "type": "string" + }, + "examples": { + "tools": { + "value": "tools", + "summary": "工具类" + } + } + }, + { + "name": "filter.license", + "in": "query", + "description": "按许可证筛选", + "required": false, + "schema": { + "type": "string" + }, + "examples": { + "apache": { + "value": "Apache License 2.0", + "summary": "Apache 2.0 许可证" + } + } + }, + { + "name": "filter.custom_tag", + "in": "query", + "description": "按自定义标签筛选", + "required": false, + "schema": { + "type": "string" + }, + "examples": { + "weather": { + "value": "weather", + "summary": "天气" + } + } + }, + { + "name": "filter.owner", + "in": "query", + "description": "按所有者筛选(用户名或组织名)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "排序方式:default(综合)/ created_at(创建时间)/ last_modified(最近更新)/ views(浏览量)/ downloads(下载量)/ likes(喜欢数)", + "required": false, + "schema": { + "type": "string", + "enum": [ + "default", + "created_at", + "last_modified", + "views", + "downloads", + "likes" + ], + "default": "default", + "examples": [ + "default", + "created_at", + "last_modified", + "views", + "downloads", + "likes" + ] + } + }, + { + "name": "page_number", + "in": "query", + "description": "页码,从 1 开始", + "required": false, + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "page_size", + "in": "query", + "description": "每页条数,取值 1~100", + "required": false, + "schema": { + "type": "integer", + "default": 10 + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/list-agents-success" + }, + "400": { + "$ref": "#/components/responses/agent-bad-request" + }, + "401": { + "$ref": "#/components/responses/agent-unauthorized" + }, + "403": { + "$ref": "#/components/responses/agent-quota-limit-exceeded" + }, + "404": { + "$ref": "#/components/responses/agent-list-failed" + } + } + }, + "post": { + "operationId": "createAgent", + "summary": "创建 Agent", + "description": "创建一个新的 Agent。\n\n使用 `owner` 和 `agent_name` 指定 Agent 所有者与唯一名称。\n\n支持设置框架类型(`framework`)、是否私有(`private`)与自定义标签(`custom_tags`)等配置。\n\n请求字段与查看 Agent 详情接口保持同名(`logo_url` / `private` / `custom_tags` 等)。\n", + "tags": [ + "AgentHub" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAgentRequest" + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/create-agent-success" + }, + "400": { + "$ref": "#/components/responses/agent-bad-request" + }, + "401": { + "$ref": "#/components/responses/agent-unauthorized" + }, + "500": { + "$ref": "#/components/responses/agent-internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/agents/{id}": { + "get": { + "operationId": "getAgent", + "summary": "查看 Agent 详情", + "description": "根据 Agent ID 返回指定 Agent 的详细信息,包括关联的模型、技能、MCP 配置等", + "tags": [ + "AgentHub" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/get-agent-success" + }, + "400": { + "$ref": "#/components/responses/agent-bad-request" + }, + "403": { + "$ref": "#/components/responses/agent-forbidden" + }, + "404": { + "$ref": "#/components/responses/agent-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/files/upload": { + "post": { + "operationId": "uploadFile", + "summary": "上传文件", + "description": "通用文件上传接口(最大 5MB),返回文件 ID。该 ID 可作为入参传给其他接口,用于引用已上传的文件。", "tags": [ "Files" ], @@ -3286,1247 +3854,1283 @@ } ] } - } - }, - "components": { - "schemas": { - "User": { - "allOf": [ - { - "$ref": "#/components/schemas/UserPublic" + }, + "/models/{owner}/{repo_name}/settings": { + "patch": { + "operationId": "updateModelSettings", + "summary": "更新模型通用设置", + "description": "局部更新模型通用设置。省略不变,显式空值清空。gated_mode 取 auto 自动审批、manual 人工审批;显式 visibility=\"gated\" 时,gated_mode 省略或空字符串默认 auto;两字段均省略保持原设置;无论 visibility 取值,显式 null、布尔值、数字及其他非法值均返回 400;visibility 为 public/private 时禁止传 gated_mode(含空串和 null),否则返回 400;PATCH 省略 visibility 时,仅当前仓库已是申请制才可用 gated_mode 修改审批方式。关闭申请制请将 visibility 改为 public 或 private。通用设置不包含专区字段。所有响应均有 request_id。", + "tags": [ + "Models" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetModelCategoryResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/bad-request" + }, + "401": { + "$ref": "#/components/responses/unauthorized" + }, + "403": { + "$ref": "#/components/responses/forbidden" + }, + "404": { + "$ref": "#/components/responses/not-found" }, + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [ { - "$ref": "#/components/schemas/UserPrivate" + "bearerAuth": [] } ], - "description": "用户完整信息(包含私有信息)" - }, - "UserResponse": { - "type": "object", - "description": "用户信息响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功响应时始终为 true", - "examples": [ - true - ] - }, - "data": { - "$ref": "#/components/schemas/User", - "description": "用户信息" + "parameters": [ + { + "$ref": "#/components/parameters/owner" }, - "request_id": { - "type": "string", - "description": "请求ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" - ] + { + "$ref": "#/components/parameters/repo_name" } - }, - "required": [ - "success", - "request_id" - ] - }, - "ErrorResponse": { - "type": "object", - "description": "错误响应格式", - "properties": { - "success": { - "type": "boolean", - "description": "错误响应时始终为 false", - "examples": [ - false - ] + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelSettingsRequest" + }, + "example": { + "description": null, + "visibility": "gated", + "gated_mode": "manual", + "task": "text-generation" + } + } + } + } + } + }, + "/datasets/{owner}/{repo_name}/category": { + "put": { + "operationId": "setDatasetCategory", + "summary": "设置数据集类型", + "description": "设置或切换数据集类型。science 必须提供匹配的 primary_subject_area 和 secondary_specialization,枚举查询 GET /dataset/science/options;base 清除科学专区字段。不支持 AIGC 字段。需要仓库维护权限,所有响应均有 request_id。", + "tags": [ + "Datasets" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetWriteResponse" + } + } + } }, - "code": { - "type": "string", - "description": "错误码", - "examples": [ - "InputParameterError" - ] + "400": { + "$ref": "#/components/responses/bad-request" }, - "message": { - "type": "string", - "description": "错误消息", - "examples": [ - "Unauthorized access" - ] + "401": { + "$ref": "#/components/responses/unauthorized" }, - "request_id": { - "type": "string", - "description": "请求ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" - ] + "403": { + "$ref": "#/components/responses/forbidden" + }, + "404": { + "$ref": "#/components/responses/not-found" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" } }, - "required": [ - "success", - "code", - "message", - "request_id" - ] + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" + }, + { + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetCategoryRequest" + }, + "example": { + "dataset_category": "science", + "primary_subject_area": "life-sciences", + "secondary_specialization": "dna" + } + } + } + } }, - "Model": { - "type": "object", - "description": "模型摘要信息", - "properties": { - "id": { - "type": "string", - "description": "模型仓库名(repo_id,格式为 owner/repo_name)", - "examples": [ - "iic/QwenLong-L1.5-30B-A3B", - "ZhipuAI/AutoGLM-Phone-9B" - ] + "patch": { + "operationId": "updateDatasetCategory", + "summary": "局部更新科学数据集字段", + "description": "仅更新当前数据集专区字段;类型由服务端从数据库读取当前仓库确定,不接受 dataset_category(传入返回 400),改变类型请用 PUT /category。省略不变,显式空值清空,学科必填约束仍需满足;cover_image 可用 null 清空。不适用于数据库当前类型的字段返回 400,message 列出当前类型及所有不适用字段;显式 null 也按传入字段校验。所有响应均有 request_id。", + "tags": [ + "Datasets" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetWriteResponse" + } + } + } }, - "display_name": { - "type": [ - "string", - "null" - ], - "description": "模型展示名称", - "examples": [ - "QwenLong-L1.5-30B-A3B", - "AutoGLM-Phone-9B" - ] + "400": { + "$ref": "#/components/responses/bad-request" }, - "description": { - "type": [ - "string", - "null" - ], - "description": "模型简介", - "examples": [ - "" - ] + "401": { + "$ref": "#/components/responses/unauthorized" }, - "downloads": { - "type": "integer", - "description": "下载量", - "examples": [ - 1466, - 28525 - ] + "403": { + "$ref": "#/components/responses/forbidden" }, - "likes": { - "type": "integer", - "description": "喜欢量", - "examples": [ - 25, - 265 - ] + "404": { + "$ref": "#/components/responses/not-found" }, - "license": { - "type": [ - "string", - "null" - ], - "description": "许可证", - "examples": [ - "apache-2.0", - "MIT License" - ] + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" }, - "tasks": { - "type": "array", - "description": "任务列表", - "items": { - "type": "string" - }, - "examples": [ - [ - "text-generation" - ], - [ - "image-captioning" - ] - ] + { + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateDatasetCategoryRequest" + }, + "example": { + "cover_image": null + } + } + } + } + } + }, + "/datasets/{owner}/{repo_name}/settings": { + "patch": { + "operationId": "updateDatasetSettings", + "summary": "更新数据集通用设置", + "description": "局部更新数据集通用设置。省略不变,显式空值清空。gated_mode 取 auto 自动审批、manual 人工审批;显式 visibility=\"gated\" 时,gated_mode 省略或空字符串默认 auto;两字段均省略保持原设置;无论 visibility 取值,显式 null、布尔值、数字及其他非法值均返回 400;visibility 为 public/private 时禁止传 gated_mode(含空串和 null),否则返回 400;PATCH 省略 visibility 时,仅当前仓库已是申请制才可用 gated_mode 修改审批方式。关闭申请制请将 visibility 改为 public 或 private。所有响应均有 request_id。", + "tags": [ + "Datasets" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetWriteResponse" + } + } + } }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间(ISO 8601 UTC)", - "examples": [ - "2025-12-14T12:26:15Z" - ] + "400": { + "$ref": "#/components/responses/bad-request" }, - "last_modified": { - "type": "string", - "format": "date-time", - "description": "Git 文件或设置的最后修改时间(ISO 8601 UTC)", - "examples": [ - "2025-12-16T03:34:26Z" - ] + "401": { + "$ref": "#/components/responses/unauthorized" }, - "file_size": { - "type": "integer", - "description": "文件大小(字节)", - "examples": [ - 61081671123 - ] + "403": { + "$ref": "#/components/responses/forbidden" }, - "params": { - "type": "integer", - "description": "参数量", - "examples": [ - 30532122624 - ] + "404": { + "$ref": "#/components/responses/not-found" }, - "tags": { - "type": "array", - "description": "标签列表", - "items": { - "type": "string" - }, - "examples": [ - [ - "license:apache-2.0", - "model_type:qwen3_moe", - "library:transformer", - "library:safetensors", - "library:pytorch", - "task:text-generation", - "deploy:swingdeploy" - ] - ] - }, - "private": { - "type": "boolean", - "description": "是否非公开模型", - "examples": [ - false - ] - }, - "gated": { - "type": "boolean", - "description": "是否申请制模型,通过申请后可访问", - "examples": [ - false - ] + "500": { + "$ref": "#/components/responses/internal-server-error" } }, - "required": [ - "id", - "downloads", - "likes", - "tasks", - "created_at", - "last_modified", - "file_size", - "params", - "private", - "gated" - ] - }, - "ModelList": { - "type": "object", - "description": "模型列表与分页信息", - "properties": { - "models": { - "type": "array", - "description": "结果模型列表", - "items": { - "$ref": "#/components/schemas/Model" + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" + }, + { + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetSettingsRequest" + }, + "example": { + "description": null, + "visibility": "gated", + "gated_mode": "manual" + } + } + } + } + } + }, + "/models/{owner}/{repo_name}/files": { + "delete": { + "operationId": "deleteModelFiles", + "summary": "单次 commit 原子批量删除文件", + "description": "批量删除仓库文件,整个请求只产生一次 commit;任一文件不存在或校验失败时全部失败,不允许部分成功。file_paths 为 1–100 个不重复的仓库相对文件路径,不接受目录或越界路径。revision 默认为 master,只能指定分支,不能指定 tag。成功返回 commit_sha 和完整 deleted_files,failed_files 为空;所有响应均有 request_id。", + "tags": [ + "Models" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFilesResponse" + } + } } }, - "total_count": { - "type": "integer", - "description": "符合筛选条件的模型总数", - "examples": [ - 43127 - ] + "400": { + "$ref": "#/components/responses/bad-request" }, - "page_number": { - "type": "integer", - "description": "当前页码", - "examples": [ - 1 - ] + "401": { + "$ref": "#/components/responses/unauthorized" }, - "page_size": { - "type": "integer", - "description": "每页大小", - "examples": [ - 10 - ] - } - }, - "required": [ - "models", - "total_count", - "page_number", - "page_size" - ] - }, - "ModelListResponse": { - "type": "object", - "description": "列出模型的标准响应包裹格式", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + "403": { + "$ref": "#/components/responses/forbidden" }, - "data": { - "$ref": "#/components/schemas/ModelList", - "description": "模型列表数据" + "404": { + "$ref": "#/components/responses/not-found" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" - ] + "500": { + "$ref": "#/components/responses/internal-server-error" } }, - "required": [ - "success", - "data", - "request_id" - ] - }, - "ModelDetail": { - "allOf": [ + "security": [ { - "$ref": "#/components/schemas/Model" + "bearerAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" }, { - "type": "object", - "description": "模型详情信息", - "properties": { - "readme": { - "type": [ - "string", - "null" + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFilesRequest" + }, + "example": { + "file_paths": [ + "obsolete/a.txt", + "obsolete/b.txt" ], - "description": "README 内容", - "examples": [ - "hello" - ] + "revision": "master", + "commit_message": "Remove obsolete files" } } } - ] - }, - "ModelDetailResponse": { - "type": "object", - "description": "获取模型详情的标准响应包裹格式", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + } + } + }, + "/datasets/{owner}/{repo_name}/files": { + "delete": { + "operationId": "deleteDatasetFiles", + "summary": "单次 commit 原子批量删除文件", + "description": "批量删除仓库文件,整个请求只产生一次 commit;任一文件不存在或校验失败时全部失败,不允许部分成功。file_paths 为 1–100 个不重复的仓库相对文件路径,不接受目录或越界路径。revision 默认为 master,只能指定分支,不能指定 tag。成功返回 commit_sha 和完整 deleted_files,failed_files 为空;所有响应均有 request_id。", + "tags": [ + "Datasets" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFilesResponse" + } + } + } }, - "data": { - "$ref": "#/components/schemas/ModelDetail", - "description": "模型详情数据" + "400": { + "$ref": "#/components/responses/bad-request" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" - ] + "401": { + "$ref": "#/components/responses/unauthorized" + }, + "403": { + "$ref": "#/components/responses/forbidden" + }, + "404": { + "$ref": "#/components/responses/not-found" + }, + "500": { + "$ref": "#/components/responses/internal-server-error" } }, - "required": [ - "success", - "data", - "request_id" - ] - }, - "Dataset": { - "type": "object", - "description": "数据集摘要信息", - "properties": { - "id": { - "type": "string", - "description": "数据集仓库名(repo_id,格式为 owner/repo_name)", - "examples": [ - "DAMO_NLP/jd", - "iic/nlp_domain_classification_chinese_testset" - ] + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/owner" }, - "display_name": { - "type": [ - "string", - "null" - ], - "description": "数据集展示名称", - "examples": [ - "商品评论情感预测", - "中文文本领域分类测试集" - ] - }, - "description": { - "type": [ - "string", - "null" - ], - "description": "数据集简介", - "examples": [ - "", - "..." - ] - }, - "file_size": { - "type": "integer", - "description": "文件大小(字节)", - "examples": [ - 0 - ] + { + "$ref": "#/components/parameters/repo_name" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteFilesRequest" + }, + "example": { + "file_paths": [ + "obsolete/a.txt", + "obsolete/b.txt" + ], + "revision": "master", + "commit_message": "Remove obsolete files" + } + } + } + } + } + }, + "/dataset/science/options": { + "get": { + "operationId": "listScienceDatasetOptions", + "summary": "获取科学数据集学科枚举", + "description": "获取科学数据集学科枚举。 所有层级的选项使用 label 作为展示名称,一级学科和二级专业方向均使用 id 作为提交值,分别用于 primary_subject_area 和 secondary_specialization。", + "tags": [ + "Datasets" + ], + "responses": { + "200": { + "description": "成功;所有响应含 request_id。", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScienceDatasetOptionsResponse" + }, + "examples": { + "science-dataset-options-response": { + "$ref": "#/components/examples/science-dataset-options-response" + } + } + } + } }, - "downloads": { - "type": "integer", - "description": "下载量", - "examples": [ - 53996, - 2401 - ] + "400": { + "$ref": "#/components/responses/bad-request" }, - "likes": { - "type": "integer", - "description": "喜欢量", - "examples": [ - 102, - 23 - ] + "401": { + "$ref": "#/components/responses/unauthorized" }, - "license": { - "type": [ - "string", - "null" - ], - "description": "许可证", - "examples": [ - "Apache License 2.0", - "cc-by-4.0" - ] + "403": { + "$ref": "#/components/responses/forbidden" }, - "tasks": { - "type": "array", - "description": "任务列表", - "items": { - "type": "string" - }, - "examples": [ - [ - "text-classification" - ], - [] - ] + "404": { + "$ref": "#/components/responses/not-found" }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "创建时间(ISO 8601 UTC)", - "examples": [ - "2022-09-28T06:30:31Z", - "2025-11-25T14:30:22Z" - ] + "500": { + "$ref": "#/components/responses/internal-server-error" + } + }, + "security": [] + } + }, + "/models/options": { + "get": { + "operationId": "getModelOptions", + "summary": "获取模型任务与许可证选项", + "description": "返回 tasks 和 license 两个列表,每项仅包含 type、id、label。tasks[].id 用于创建模型、更新模型设置的 task 或列表筛选 filter.task;license[].id 用于创建模型的 license,label 是可用于各处直接展示的名称。", + "tags": [ + "Models" + ], + "responses": { + "200": { + "description": "Success. Every response includes request_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelOptionsResponse" + } + } + } }, - "last_modified": { - "type": "string", - "format": "date-time", - "description": "Git 文件或设置的最后修改时间(ISO 8601 UTC)", - "examples": [ - "2022-10-30T03:36:39Z", - "2026-01-01T00:31:42Z" - ] + "400": { + "$ref": "#/components/responses/bad-request" }, - "tags": { - "type": "array", - "description": "标签列表", - "items": { - "type": "string" - }, - "examples": [ - [ - "license:Apache License 2.0", - "task:text-classification" - ], - [ - "license:cc-by-4.0", - "custom_tag:common-crawl" - ] - ] + "401": { + "$ref": "#/components/responses/unauthorized" }, - "private": { - "type": "boolean", - "description": "是否非公开数据集", - "examples": [ - false - ] + "403": { + "$ref": "#/components/responses/forbidden" }, - "gated": { - "type": "boolean", - "description": "是否申请制数据集,通过申请后可访问", - "examples": [ - false - ] + "404": { + "$ref": "#/components/responses/not-found" }, - "login_required": { - "type": "boolean", - "description": "是否登录后才可下载", - "examples": [ - false - ] + "500": { + "$ref": "#/components/responses/internal-server-error" } }, - "required": [ - "id", - "downloads", - "likes", - "private", - "gated", - "login_required", - "created_at", - "last_modified" - ] - }, - "DatasetList": { - "type": "object", - "description": "数据集列表与分页信息", - "properties": { - "datasets": { - "type": "array", - "description": "结果数据集列表", - "items": { - "$ref": "#/components/schemas/Dataset" + "security": [] + } + }, + "/agent/playground/instances": { + "post": { + "operationId": "createAgentPlaygroundInstance", + "summary": "创建实验场实例", + "description": "创建一个实验场(Agent Playground)实例。\n\n异步创建:响应中 `status` 为 `starting`,就绪后转为 `running`。\n\n可选的 `models` / `mcps` / `skills` / `message` 会在实例就绪后按「模型 → MCP → Skill → 对话」的顺序\n作用于实例内 `default` Agent。引用在创建时同步校验,不合法直接返回 400;应用阶段失败的项会被跳过\n(实例照常转为 `running`),错误经一次性通知(`init_error`)返回。\n\n不传 `models` 时会自动配置平台默认模型。\n\n实例数超出账号配额时返回 400。\n", + "tags": [ + "Agent Playground" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAgentPlaygroundInstanceRequest" + }, + "examples": { + "create-agent-playground-instance-request": { + "$ref": "#/components/examples/create-agent-playground-instance-request" + } + } } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/create-agent-playground-instance-success" }, - "total_count": { - "type": "integer", - "description": "符合筛选条件的数据集总数", - "examples": [ - 60 - ] + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "page_number": { - "type": "integer", - "description": "当前页码", - "examples": [ - 1 - ] + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "page_size": { - "type": "integer", - "description": "每页大小", - "examples": [ - 10 - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "409": { + "$ref": "#/components/responses/agent-playground-conflict" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } }, - "required": [ - "datasets", - "total_count", - "page_number", - "page_size" + "security": [ + { + "bearerAuth": [] + } ] }, - "DatasetListResponse": { - "type": "object", - "description": "列出数据集的标准响应包裹格式", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + "get": { + "operationId": "listAgentPlaygroundInstances", + "summary": "获取实验场实例列表", + "description": "分页返回调用者名下的实验场实例。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "name": "owner", + "in": "query", + "description": "实例归属账号,缺省为调用者;只能是调用者本人,否则返回 403", + "required": false, + "schema": { + "type": "string", + "examples": [ + "username" + ] + } }, - "data": { - "$ref": "#/components/schemas/DatasetList", - "description": "数据集列表数据" + { + "name": "page_number", + "in": "query", + "description": "页码,需大于 0;非数字或小于等于 0 返回 400", + "required": false, + "schema": { + "type": "integer", + "default": 1, + "minimum": 1 + } }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" - ] - } - }, - "required": [ - "success", - "data", - "request_id" - ] - }, - "DatasetDetail": { - "allOf": [ { - "$ref": "#/components/schemas/Dataset" + "name": "page_size", + "in": "query", + "description": "每页条数,取值 1~100;越界或非数字返回 400", + "required": false, + "schema": { + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100 + } }, { - "type": "object", - "description": "数据集详情信息", - "properties": { - "readme": { - "type": [ - "string", - "null" - ], - "description": "README 内容", - "examples": [ - "hello" - ] - } + "name": "sort_by", + "in": "query", + "description": "排序字段,大小写不敏感;`user_modified_at` 为用户最近操作时间(相同时按创建时间)", + "required": false, + "schema": { + "type": "string", + "enum": [ + "user_modified_at", + "gmt_created" + ], + "default": "user_modified_at" + } + }, + { + "name": "order", + "in": "query", + "description": "排序方向", + "required": false, + "schema": { + "type": "string", + "enum": [ + "desc", + "asc" + ], + "default": "desc" } } - ] - }, - "DatasetDetailResponse": { - "type": "object", - "description": "获取数据集详情的标准响应包裹格式", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + ], + "responses": { + "200": { + "$ref": "#/components/responses/list-agent-playground-instances-success" }, - "data": { - "$ref": "#/components/schemas/DatasetDetail", - "description": "数据集详情数据" + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" - ] + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" + }, + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } }, - "required": [ - "success", - "data", - "request_id" + "security": [ + { + "bearerAuth": [] + } ] - }, - "McpServerLocale": { - "type": "object", - "description": "MCP服务在对应语言环境下的内容", - "properties": { - "name": { - "type": "string", - "description": "MCP服务在对应语言环境下的服务名称", - "examples": [ - "Fetch网页内容抓取" - ] + } + }, + "/agent/playground/instances/from-agent-hub": { + "post": { + "operationId": "createAgentPlaygroundInstanceFromAgentHub", + "summary": "从 Agent Hub 创建实验场实例", + "description": "创建实例,并把 Agent Hub 上指定 agent 的内容同步到实例内 `default` Agent(覆盖写入)。\n\n与普通创建的区别:\n\n- 内容同步目前只支持 `qwenpaw` / `ms-agent`。\n- **不会**自动配置默认模型:未传 `models` 时实例内模型取决于源 agent 的内容,可能为空。\n- 同步完成后产生一次性通知(`import_result`;带 `message` 时为 `conversation`),payload 里标明来源与去向。\n\n其余字段的校验与普通创建一致。\n", + "tags": [ + "Agent Playground" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAgentPlaygroundInstanceFromAgentHubRequest" + }, + "examples": { + "create-agent-playground-instance-from-agent-hub-request": { + "$ref": "#/components/examples/create-agent-playground-instance-from-agent-hub-request" + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/create-agent-playground-instance-from-agent-hub-success" }, - "description": { - "type": "string", - "description": "MCP服务在对应语言环境下的服务简介", - "examples": [ - "该服务器使大型语言模型能够检索和处理网页内容..." - ] + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "readme": { - "type": "string", - "description": "MCP服务介绍(仅详情接口返回)", - "examples": [ - "# Fetch MCP Server..." - ] - } - } - }, - "McpServerLocales": { - "type": "object", - "description": "不同语言环境下的内容展示", - "properties": { - "zh": { - "$ref": "#/components/schemas/McpServerLocale", - "description": "中文环境" + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "en": { - "$ref": "#/components/schemas/McpServerLocale", - "description": "英文环境" - } - } - }, - "McpOperationalUrl": { - "type": "object", - "description": "MCP服务托管链接信息", - "properties": { - "id": { - "type": "string", - "description": "MCP服务链接命名标识", - "examples": [ - "platform-pool" - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "url": { - "type": "string", - "description": "MCP服务远程连接地址", - "examples": [ - "https://mcp.api-inference.modelscope.net/xxxx/mcp" - ] + "404": { + "$ref": "#/components/responses/agent-playground-not-found" }, - "transport_type": { - "type": "string", - "description": "MCP服务链接传输方式", - "examples": [ - "streamable_http", - "sse" - ] + "409": { + "$ref": "#/components/responses/agent-playground-conflict" }, - "auth_required": { - "type": "boolean", - "description": "远程 url 连接时是否需要提供魔搭访问令牌鉴权", - "examples": [ - false - ] + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/agent/playground/instances/{uuid}": { + "get": { + "operationId": "getAgentPlaygroundInstance", + "summary": "获取实验场实例详情", + "description": "按 `uuid` 返回实例详情。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/get-agent-playground-instance-success" }, - "expiration": { - "type": "string", - "description": "MCP服务链接有效期", - "examples": [ - "2025-10-01 21:00:00" - ] + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "accessible": { - "type": "boolean", - "description": "是否有权限访问该MCP服务详情。当所有者设置为非公开或MCP服务被删除时,无权访问。", - "examples": [ - true - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } - } + }, + "security": [ + { + "bearerAuth": [] + } + ] }, - "McpServerSummary": { - "type": "object", - "description": "MCP服务摘要信息", - "properties": { - "id": { - "type": "string", - "description": "服务ID。由平台后台收录的MCP,ID 默认为@author/server_name,当被社区用户认领后将会变更为user_name/server_name;由社区用户自发提交的MCP,ID 为user_name/server_name。", - "examples": [ - "@modelcontextprotocol/fetch" - ] - }, - "name": { - "type": "string", - "description": "MCP服务在魔搭社区的名称", - "examples": [ - "Fetch网页内容抓取" - ] - }, - "chinese_name": { - "type": "string", - "description": "MCP服务在魔搭社区的中文名称", - "examples": [ - "Fetch网页内容抓取" - ] - }, - "description": { - "type": "string", - "description": "MCP服务简介", - "examples": [ - "该服务器使大型语言模型能够检索和处理网页内容..." - ] + "put": { + "operationId": "updateAgentPlaygroundInstance", + "summary": "更新实验场实例元信息", + "description": "只允许修改 `instance_name`、`display_name`、`description`、`logo`,其余字段不可改。\n\n覆盖语义:`display_name`、`description` 缺省或传空串都会清空;`logo` 缺省保持原值,显式传入则覆盖。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAgentPlaygroundInstanceRequest" + }, + "examples": { + "update-agent-playground-instance-request": { + "$ref": "#/components/examples/update-agent-playground-instance-request" + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/update-agent-playground-instance-success" }, - "logo_url": { - "type": "string", - "description": "logo图片URL链接", - "examples": [ - "https://resources.modelscope.cn/..." - ] + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "publisher": { - "type": "string", - "description": "发布时原始ID", - "examples": [ - "@modelcontextprotocol/fetch" - ] + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "categories": { - "type": "array", - "description": "该MCP服务所属分类", - "items": { - "type": "string" - }, - "examples": [ - [ - "browser-automation" - ] - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "tags": { - "type": "array", - "description": "该MCP服务的标签列表", - "items": { - "type": "string" - }, - "examples": [ - [ - "browser-automation" - ] - ] + "404": { + "$ref": "#/components/responses/agent-playground-not-found" }, - "view_count": { - "type": "integer", - "description": "该MCP服务的累计访问量", - "examples": [ - 30667 - ] + "409": { + "$ref": "#/components/responses/agent-playground-conflict" }, - "locales": { - "$ref": "#/components/schemas/McpServerLocales" + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } }, - "required": [ - "id" - ] - }, - "McpServerDetail": { - "type": "object", - "description": "MCP服务详细信息", - "allOf": [ - { - "$ref": "#/components/schemas/McpServerSummary" - }, + "security": [ { - "type": "object", - "properties": { - "author": { - "type": "string", - "description": "MCP服务开发者", - "examples": [ - "modelcontextprotocol" - ] - }, - "owner": { - "type": "string", - "description": "MCP服务归属的魔搭用户/组织ID", - "examples": [ - "" - ] - }, - "readme": { - "type": "string", - "description": "MCP服务介绍", - "examples": [ - "# Fetch MCP Server..." - ] - }, - "source_url": { - "type": "string", - "description": "MCP服务来源项目主页地址", - "examples": [ - "https://github.com/modelcontextprotocol/..." - ] - }, - "github_stars": { - "type": "integer", - "description": "该 MCP 服务对应 github 原始仓库的 stars 数量", - "examples": [ - 1234 - ] - }, - "is_hosted": { - "type": "boolean", - "description": "该MCP服务是否支持托管部署", - "examples": [ - true - ] - }, - "is_verified": { - "type": "boolean", - "description": "托管MCP服务是否经过平台验证测试", - "examples": [ - true - ] - }, - "env_schema": { - "type": "object", - "description": "该服务连接部署的环境变量配置", - "additionalProperties": true - }, - "server_config": { - "type": "array", - "description": "MCP服务配置信息", - "items": {} - }, - "operational_urls": { - "type": "array", - "description": "当get_operational_url=True且当前用户已连接该 MCP 服务时则返回,否则为空", - "items": { - "$ref": "#/components/schemas/McpOperationalUrl" - } - } - } + "bearerAuth": [] } ] }, - "McpServerOperational": { - "type": "object", - "description": "用户托管的MCP服务信息", - "allOf": [ + "delete": { + "operationId": "deleteAgentPlaygroundInstance", + "summary": "删除实验场实例", + "description": "删除实例,同时清除该实例的通知。\n\n幂等:对不存在或删除中的实例同样返回 200 且 `deleted: true`。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ { - "$ref": "#/components/schemas/McpServerSummary" + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/delete-agent-playground-instance-success" + }, + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" + }, + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + } + }, + "security": [ { - "type": "object", - "properties": { - "operational_urls": { - "type": "array", - "description": "MCP服务托管链接列表", - "items": { - "$ref": "#/components/schemas/McpOperationalUrl" - } - } - } + "bearerAuth": [] } ] - }, - "McpServerListResponse": { - "type": "object", - "description": "MCP服务列表响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + } + }, + "/agent/playground/instances/{uuid}/pause": { + "post": { + "operationId": "pauseAgentPlaygroundInstance", + "summary": "暂停实验场实例", + "description": "暂停实例,工作区内容保留。\n\n任意状态都可调用且幂等,响应 `status` 立即为 `sleeping`。\n\n实例空闲一段时间后也会被系统自动休眠,自动休眠不更新 `user_modified_at`。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/pause-agent-playground-instance-success" }, - "data": { - "$ref": "#/components/schemas/McpServerList", - "description": "MCP服务列表数据" + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "message": { - "type": "string", - "description": "响应消息", - "examples": [ - "success" - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" - ] + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } }, - "required": [ - "success", - "request_id" + "security": [ + { + "bearerAuth": [] + } ] - }, - "McpServerOperationalListResponse": { - "type": "object", - "description": "用户托管MCP服务列表响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + } + }, + "/agent/playground/instances/{uuid}/resume": { + "post": { + "operationId": "resumeAgentPlaygroundInstance", + "summary": "恢复实验场实例", + "description": "恢复实例运行。任意状态都可调用(幂等)。\n\n响应 `status` 按调用前的状态给出:\n\n- `sleeping`:返回 `running`。唤醒通常秒级完成,期间查询详情可能短暂返回 `starting`。\n- `starting` / `failed`:返回 `starting`。\n- `running`:返回 `running`。\n\n`failed` 实例恢复不会触发重建,请改用重启接口。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/resume-agent-playground-instance-success" }, - "data": { - "$ref": "#/components/schemas/McpServerOperationalList", - "description": "用户托管MCP服务列表数据" + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "message": { - "type": "string", - "description": "响应消息", - "examples": [ - "success" - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" - ] + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } }, - "required": [ - "success", - "request_id" + "security": [ + { + "bearerAuth": [] + } ] - }, - "McpServerDetailResponse": { - "type": "object", - "description": "MCP服务详情响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + } + }, + "/agent/playground/instances/{uuid}/restart": { + "post": { + "operationId": "restartAgentPlaygroundInstance", + "summary": "冷重启实验场实例", + "description": "冷重启:实例内进程与内存状态重置,工作区内容保留。\n\n任意状态都可调用,`sleeping` / `failed` 实例会被拉起。异步操作,响应 `status` 为 `starting`。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/restart-agent-playground-instance-success" }, - "data": { - "$ref": "#/components/schemas/McpServerDetail", - "description": "MCP服务详情数据" + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "message": { - "type": "string", - "description": "响应消息", - "examples": [ - "success" - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" - ] + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" } }, - "required": [ - "success", - "request_id" + "security": [ + { + "bearerAuth": [] + } ] - }, - "McpDeployResponse": { - "type": "object", - "description": "MCP服务部署响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + } + }, + "/agent/playground/instances/{uuid}/agents": { + "get": { + "operationId": "getAgentPlaygroundInstanceAgents", + "summary": "获取实例内 Agent 信息", + "description": "返回实例内所有 Agent 的概要(`summary`)与详情(`detail`)。用于确认下发结果,以及拿到下发接口需要的 `agent_id`。\n\n休眠实例返回最近一次已知的数据,不会被唤醒;实例还在启动时返回空列表而不是报错,可以直接轮询;\n实例内 Agent 服务尚未就绪时返回 503,稍后重试。\n\n框架差异:qwenpaw 可有多个 Agent,persona 为工作区中的 Markdown 文件,`mcp_clients.tools` 仅对启用的客户端返回;\nms-agent 恒为 1 个 `default` Agent,persona 固定 `AGENTS.md`、`PROFILE.md`,`mcp_clients.tools` 恒为空数组。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/get-agent-playground-instance-agents-success" }, - "data": { - "$ref": "#/components/schemas/McpOperationalUrl", - "description": "部署后的MCP服务链接信息" + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "message": { - "type": "string", - "description": "响应消息", - "examples": [ - "success" - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" - ] + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + }, + "502": { + "$ref": "#/components/responses/agent-playground-bad-gateway" + }, + "503": { + "$ref": "#/components/responses/agent-playground-service-unavailable" } }, - "required": [ - "success", - "request_id" + "security": [ + { + "bearerAuth": [] + } ] - }, - "McpUndeployResponse": { - "type": "object", - "description": "MCP服务解除部署响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] + } + }, + "/agent/playground/instances/{uuid}/setup/model": { + "post": { + "operationId": "setupAgentPlaygroundModel", + "summary": "向实例下发模型配置", + "description": "为目标 Agent 配置模型:按模型引用解析后下发给运行中的实例,并**设为该 Agent 的生效模型**。\n重复下发同一个模型是幂等的。\n\n下发类接口的公共约定:\n\n- 调用者须是实例 owner,且实例 `status` 为 `running`,否则返回 400(休眠实例请先恢复)。\n- 同步下发,最长等待 50 秒,超时返回 504(操作可能仍在实例内继续完成)。\n- 实例内返回的错误会透传,其余实例内失败返回 503。\n- 成功返回最新的实例对象,下发不改变实例 `status`。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetupAgentPlaygroundModelRequest" + }, + "examples": { + "setup-agent-playground-model-request": { + "$ref": "#/components/examples/setup-agent-playground-model-request" + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/setup-agent-playground-instance-success" }, - "message": { - "type": "string", - "description": "响应消息", - "examples": [ - "success" - ] + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" - ] + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" + }, + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + }, + "502": { + "$ref": "#/components/responses/agent-playground-bad-gateway" + }, + "503": { + "$ref": "#/components/responses/agent-playground-service-unavailable" + }, + "504": { + "$ref": "#/components/responses/agent-playground-request-timeout" } }, - "required": [ - "success", - "request_id" - ] - }, - "StudioHardware": { - "type": "string", - "description": "硬件配置,修改后需重新部署才能生效。\n可用值不固定,请通过 `GET /studios/hardware` 接口查询当前可用的硬件配置列表。\n免费资源常见格式为 `platform/...`、`xgpu/...`、`amd/...`;付费资源格式为 `paid/`,例如 `paid/ecs.gn7i-c8g1.2xlarge`。\n", - "examples": [ - "platform/2v-cpu-16g-mem", - "paid/ecs.gn7i-c8g1.2xlarge" - ] - }, - "StudioSDKType": { - "type": "string", - "description": "SDK 类型,修改后需重新部署才能生效", - "enum": [ - "gradio", - "streamlit", - "docker", - "static" - ] - }, - "StudioSDKVersion": { - "type": "string", - "description": "SDK 版本,仅对 Gradio 类型生效,默认最新版,建议选用最新版本,修改后需重新部署才能生效。\n可用版本会随平台更新,请通过 `GET /studios/sdk-versions?sdk_type=gradio` 查询当前可用的 Gradio 版本列表。\n", - "examples": [ - "6.2.0" - ] - }, - "StudioBaseImage": { - "type": "string", - "description": "基础镜像,仅 Docker 类型不支持,建议选用最新版本,修改后需重新部署才能生效。\n可用值不固定,请通过 `GET /studios/base-images` 接口查询当前可用的基础镜像列表。\n", - "examples": [ - "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0" + "security": [ + { + "bearerAuth": [] + } ] - }, - "CreateStudioRequest": { - "type": "object", - "description": "创建 Studio 请求参数", - "properties": { - "repo_name": { - "type": "string", - "description": "仓库名称", - "maxLength": 64, - "examples": [ - "my-demo-app" - ] + } + }, + "/agent/playground/instances/{uuid}/setup/mcp": { + "post": { + "operationId": "setupAgentPlaygroundMcp", + "summary": "向实例下发 MCP 配置", + "description": "为目标 Agent 添加一个 MCP 连接,`server_config` 校验通过后原样下发。\n\n**仅新增**:目标 Agent 下已有同名 client key 时返回 409,不会覆盖。qwenpaw 会把 key 中的斜杠与反斜杠\n替换为 `-`,实例内实际 key 以实例内 Agent 信息接口为准。\n\n连接方式:带 `command` 的配置按 stdio 处理;带 `url` 且未写 `type` 时按 SSE 处理,\nStreamable HTTP 服务必须显式写 `\"type\": \"streamable_http\"`。\n\n前置条件、超时与错误透传规则同下发模型接口。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetupAgentPlaygroundMcpRequest" + }, + "examples": { + "setup-agent-playground-mcp-request": { + "$ref": "#/components/examples/setup-agent-playground-mcp-request" + } + } + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/setup-agent-playground-instance-success" }, - "owner": { - "type": "string", - "description": "所有者(用户名或组织名)", - "examples": [ - "username" - ] + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "display_name": { - "type": "string", - "description": "显示名称", - "maxLength": 128, - "examples": [ - "我的演示应用" - ] + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "license": { - "type": "string", - "description": "许可证,默认 apache-2.0", - "default": "apache-2.0", - "examples": [ - "apache-2.0" - ] + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" }, - "visibility": { - "$ref": "#/components/schemas/StudioVisibility" + "404": { + "$ref": "#/components/responses/agent-playground-not-found" }, - "private": { - "type": "boolean", - "description": "Deprecated: 请使用 visibility 字段", - "deprecated": true, - "default": false + "409": { + "$ref": "#/components/responses/agent-playground-conflict" }, - "description": { - "type": "string", - "description": "描述", - "maxLength": 2000, - "examples": [ - "这是一个演示应用" - ] + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" }, - "cover_image": { - "type": "string", - "description": "封面图 URL,为空时使用平台默认图" + "502": { + "$ref": "#/components/responses/agent-playground-bad-gateway" }, - "sdk_type": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioSDKType" + "503": { + "$ref": "#/components/responses/agent-playground-service-unavailable" + }, + "504": { + "$ref": "#/components/responses/agent-playground-request-timeout" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/agent/playground/instances/{uuid}/setup/skill": { + "post": { + "operationId": "setupAgentPlaygroundSkill", + "summary": "向实例下发 Skill", + "description": "按 Skill Hub 引用把 Skill 安装到运行中实例的目标 Agent。\n\n- 引用格式不合法返回 400(`invalid skill ref, expected path/name`);Skill Hub 中不存在返回 400(`skill not found`)。\n- 私有 Skill 只有有读权限的账号能下发(本人、组织成员、`@` 命名空间);没有权限时与「不存在」返回同一个错误。\n- **仅新增**:实例内已有同名 Skill 返回 409,不会覆盖。\n- 安装失败返回 503,超时返回 504。\n\n前置条件与错误透传规则同下发模型接口。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetupAgentPlaygroundSkillRequest" + }, + "examples": { + "setup-agent-playground-skill-request": { + "$ref": "#/components/examples/setup-agent-playground-skill-request" + } } - ], - "default": "gradio" + } + } + }, + "responses": { + "200": { + "$ref": "#/components/responses/setup-agent-playground-instance-success" }, - "sdk_version": { - "$ref": "#/components/schemas/StudioSDKVersion" + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" }, - "base_image": { - "$ref": "#/components/schemas/StudioBaseImage" + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" }, - "hardware": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioHardware" + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "409": { + "$ref": "#/components/responses/agent-playground-conflict" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + }, + "502": { + "$ref": "#/components/responses/agent-playground-bad-gateway" + }, + "503": { + "$ref": "#/components/responses/agent-playground-service-unavailable" + }, + "504": { + "$ref": "#/components/responses/agent-playground-request-timeout" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/agent/playground/instances/{uuid}/import": { + "post": { + "operationId": "importToAgentPlaygroundInstance", + "summary": "向实例导入 Model / MCP / Skill", + "description": "往已有实例追加导入 model / mcp / skill,可附带一句话 `message` 开启对话。**休眠实例会被自动唤醒**。\n\n应用顺序与创建时一致;实例内已存在的同名 MCP / Skill 视为已完成,失败的项会被跳过并经 `init_error` 通知返回。\n\n- 四个内容字段至少传一个,否则返回 400。导入内容恒作用于实例内 `default` Agent(本接口不支持 `agent_id`)。\n- 引用在受理时同步校验,不合法返回 400,`message` 指明是哪一项。\n- 成功响应 `status` 为 `starting`,完成后回到 `running`;结果经一次性通知返回(`import_result` / `conversation`)。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportToAgentPlaygroundInstanceRequest" + }, + "examples": { + "import-to-agent-playground-instance-request": { + "$ref": "#/components/examples/import-to-agent-playground-instance-request" + } } - ], - "default": "platform/2v-cpu-16g-mem", - "description": "硬件配置。未传时使用默认免费硬件配置" + } } }, - "required": [ - "repo_name", - "owner" + "responses": { + "200": { + "$ref": "#/components/responses/import-to-agent-playground-instance-success" + }, + "400": { + "$ref": "#/components/responses/agent-playground-bad-request" + }, + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" + }, + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/agent/playground/instances/{uuid}/notifications": { + "get": { + "operationId": "getAgentPlaygroundInstanceNotifications", + "summary": "拉取实例一次性通知", + "description": "返回并**消费**实例的全部未读通知(按产生顺序):同一条通知只会返回一次,适合轮询后一次性展示。\n通知保留 7 天,超期未拉取的也会被清理;实例删除时一并清除。\n\n通知类型:\n\n- `import_result`:创建或导入的内容应用完成(未附带 `message`)。从 Agent Hub 创建时另有 `agent_repo`。\n- `conversation`:内容应用完成且附带了一句话,payload 含会话信息。\n- `agent_import`:从 Agent Hub 导入 Agent 完成,payload 含新建 Agent 的 `agent_id`。\n- `init_error`:某一项内容被跳过(payload 含 `item` / `error` / `code`),实例仍会 `running`。\n- `framework_update_error`:框架版本更新失败(payload 含 `target` / `error` / `code`)。\n\n`payload` 为原样 JSON、键名不做转换;其中 `conversation` / `agent` 部分不同框架结构不同,\n用 `framework` 字段区分。\n", + "tags": [ + "Agent Playground" + ], + "parameters": [ + { + "$ref": "#/components/parameters/agent_playground_instance_uuid" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/get-agent-playground-instance-notifications-success" + }, + "401": { + "$ref": "#/components/responses/agent-playground-unauthorized" + }, + "403": { + "$ref": "#/components/responses/agent-playground-forbidden" + }, + "404": { + "$ref": "#/components/responses/agent-playground-not-found" + }, + "500": { + "$ref": "#/components/responses/agent-playground-internal-server-error" + } + }, + "security": [ + { + "bearerAuth": [] + } ] + } + } + }, + "components": { + "schemas": { + "User": { + "allOf": [ + { + "$ref": "#/components/schemas/UserPublic" + }, + { + "$ref": "#/components/schemas/UserPrivate" + } + ], + "description": "用户完整信息(包含私有信息)" }, - "CreateStudioResponse": { + "UserResponse": { "type": "object", - "description": "创建 Studio 响应", + "description": "用户信息响应", "properties": { "success": { "type": "boolean", + "description": "成功响应时始终为 true", "examples": [ true ] }, "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Studio ID (owner/repo_name)", - "examples": [ - "username/my-demo-app" - ] - }, - "repo_name": { - "type": "string", - "description": "仓库名称", - "examples": [ - "my-demo-app" - ] - }, - "display_name": { - "type": "string", - "description": "显示名称", - "examples": [ - "我的演示应用" - ] - }, - "owner": { - "type": "string", - "description": "所有者", - "examples": [ - "username" - ] - }, - "url": { - "type": "string", - "description": "Studio URL", - "examples": [ - "https://modelscope.cn/studios/username/my-demo-app" - ] - } - } + "$ref": "#/components/schemas/User", + "description": "用户信息" }, "request_id": { - "type": "string" + "type": "string", + "description": "请求ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] } }, "required": [ @@ -4534,1151 +5138,1154 @@ "request_id" ] }, - "StudioConfig": { - "type": "object", - "description": "Studio 运行时配置", - "properties": { - "hardware": { - "$ref": "#/components/schemas/StudioHardware" - }, - "base_image": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioBaseImage" - } - ], - "description": "基础镜像(Docker 类型不返回)" - }, - "sdk_type": { - "$ref": "#/components/schemas/StudioSDKType" - }, - "sdk_version": { - "type": "string", - "description": "SDK 版本(仅 gradio 类型返回)" - } - } - }, - "StudioRuntime": { + "ErrorResponse": { "type": "object", - "description": "Studio 运行时信息", + "description": "错误响应格式", "properties": { - "status": { - "type": "string", - "description": "运行状态", - "enum": [ - "Initialized", - "Building", - "BuildFailed", - "Deploying", - "DeployFailed", - "Running", - "Stopping", - "Stopped", - "Duplicating", - "Sleeping" + "success": { + "type": "boolean", + "description": "错误响应时始终为 false", + "examples": [ + false ] }, - "active_config": { - "description": "当前实际运行中的配置", - "$ref": "#/components/schemas/StudioConfig" - }, - "created_at": { + "code": { "type": "string", - "description": "部署时间" + "description": "错误码", + "examples": [ + "InputParameterError" + ] }, - "error_message": { + "message": { "type": "string", - "description": "失败信息(仅在错误状态时返回)" - } - } - }, - "GetStudioResponse": { - "type": "object", - "description": "获取 Studio 详情响应", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Studio ID (owner/repo_name)" - }, - "repo_name": { - "type": "string", - "description": "仓库名称" - }, - "display_name": { - "type": "string", - "description": "显示名称" - }, - "owner": { - "type": "string", - "description": "所有者" - }, - "description": { - "type": "string", - "description": "描述" - }, - "cover_image": { - "type": "string", - "description": "封面图 URL" - }, - "likes": { - "type": "integer", - "description": "喜欢数" - }, - "view_count": { - "type": "integer", - "description": "访问量" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "visibility": { - "$ref": "#/components/schemas/StudioVisibility" - }, - "private": { - "type": "boolean", - "description": "Deprecated: 请使用 visibility 字段", - "deprecated": true - }, - "last_modified": { - "type": "string", - "description": "最后修改时间" - }, - "sdk_type": { - "$ref": "#/components/schemas/StudioSDKType" - }, - "sdk_version": { - "$ref": "#/components/schemas/StudioSDKVersion" - }, - "hardware": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioHardware" - } - ], - "description": "期望的硬件配置(数据库中保存的值)" - }, - "base_image": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioBaseImage" - } - ], - "description": "期望的基础镜像(Docker 类型不返回)" - }, - "license": { - "type": "string", - "description": "许可证" - }, - "host": { - "type": "string", - "description": "访问地址" - }, - "mcp_support": { - "type": "boolean", - "description": "是否支持 MCP" - }, - "runtime": { - "$ref": "#/components/schemas/StudioRuntime" - } - } + "description": "错误消息", + "examples": [ + "Unauthorized access" + ] }, "request_id": { - "type": "string" + "type": "string", + "description": "请求ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] } }, "required": [ "success", + "code", + "message", "request_id" ] }, - "UpdateStudioSettingsRequest": { + "Model": { "type": "object", - "description": "更新 Studio 设置请求参数(传入哪个字段修改哪个字段)", + "description": "模型摘要信息", "properties": { - "display_name": { - "type": "string", - "description": "显示名称", - "maxLength": 128 - }, - "license": { + "id": { "type": "string", - "description": "许可证" - }, - "visibility": { - "$ref": "#/components/schemas/StudioVisibility" + "description": "模型仓库名(repo_id,格式为 owner/repo_name)", + "examples": [ + "iic/QwenLong-L1.5-30B-A3B", + "ZhipuAI/AutoGLM-Phone-9B" + ] }, - "private": { - "type": "boolean", - "description": "Deprecated: 请使用 visibility 字段", - "deprecated": true + "display_name": { + "type": [ + "string", + "null" + ], + "description": "模型展示名称", + "examples": [ + "QwenLong-L1.5-30B-A3B", + "AutoGLM-Phone-9B" + ] }, "description": { - "type": "string", - "description": "描述", - "maxLength": 2000 + "type": [ + "string", + "null" + ], + "description": "模型简介", + "examples": [ + "" + ] }, - "cover_image": { - "type": "string", - "description": "封面图 URL" + "downloads": { + "type": "integer", + "description": "下载量", + "examples": [ + 1466, + 28525 + ] }, - "sdk_type": { - "$ref": "#/components/schemas/StudioSDKType" + "likes": { + "type": "integer", + "description": "喜欢量", + "examples": [ + 25, + 265 + ] }, - "sdk_version": { - "$ref": "#/components/schemas/StudioSDKVersion" + "license": { + "type": [ + "string", + "null" + ], + "description": "许可证", + "examples": [ + "apache-2.0", + "MIT License" + ] }, - "base_image": { - "$ref": "#/components/schemas/StudioBaseImage" + "tasks": { + "type": "array", + "description": "任务列表", + "items": { + "type": "string" + }, + "examples": [ + [ + "text-generation" + ], + [ + "image-captioning" + ] + ] }, - "hardware": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioHardware" - } - ], - "description": "硬件配置,修改后需重新部署才能生效" - } - } - }, - "AddStudioSecretRequest": { - "type": "object", - "description": "添加 Studio 密文变量请求参数", - "properties": { - "key": { + "created_at": { "type": "string", - "description": "密文变量名称", - "maxLength": 128, + "format": "date-time", + "description": "创建时间(ISO 8601 UTC)", "examples": [ - "API_KEY" + "2025-12-14T12:26:15Z" ] }, - "value": { + "last_modified": { "type": "string", - "description": "密文变量值(添加后不会公开展示)", - "maxLength": 4096, + "format": "date-time", + "description": "Git 文件或设置的最后修改时间(ISO 8601 UTC)", "examples": [ - "sk-xxxxxxxxxxxx" + "2025-12-16T03:34:26Z" ] - } - }, - "required": [ - "key", - "value" - ] - }, - "UpdateStudioSecretRequest": { - "type": "object", - "description": "更新 Studio 密文变量请求参数", - "properties": { - "key": { - "type": "string", - "description": "密文变量名称", - "maxLength": 128, + }, + "file_size": { + "type": "integer", + "description": "文件大小(字节)", "examples": [ - "API_KEY" + 61081671123 ] }, - "value": { - "type": "string", - "description": "密文变量新值", - "maxLength": 4096, + "params": { + "type": "integer", + "description": "参数量", "examples": [ - "sk-xxxxxxxxxxxx" + 30532122624 ] - } - }, - "required": [ - "key", - "value" - ] - }, - "StudioSecretItem": { - "type": "object", - "description": "密文变量列表项(仅返回 key,不返回 value)", - "properties": { - "key": { - "type": "string", - "description": "密文变量名称" - } - }, - "required": [ - "key" - ] - }, - "ListStudioSecretsResponse": { - "type": "object", - "description": "获取 Studio 密文变量列表响应", - "properties": { - "success": { - "type": "boolean" }, - "data": { - "type": "object", - "properties": { - "secrets": { - "type": "array", - "items": { - "$ref": "#/components/schemas/StudioSecretItem" - } - } - } + "tags": { + "type": "array", + "description": "标签列表", + "items": { + "type": "string" + }, + "examples": [ + [ + "license:apache-2.0", + "model_type:qwen3_moe", + "library:transformer", + "library:safetensors", + "library:pytorch", + "task:text-generation", + "deploy:swingdeploy" + ] + ] }, - "request_id": { - "type": "string" + "private": { + "type": "boolean", + "description": "是否普通私有模型;申请制模型返回 false,请结合 gated 和 visibility 判断访问模式", + "examples": [ + false + ] + }, + "gated": { + "type": "boolean", + "description": "是否申请制模型,通过申请后可访问", + "examples": [ + false + ] } }, "required": [ - "success", - "data", - "request_id" + "id", + "downloads", + "likes", + "tasks", + "created_at", + "last_modified", + "file_size", + "params", + "private", + "gated" ] }, - "DeleteStudioSecretRequest": { + "ModelList": { "type": "object", - "description": "删除 Studio 密文变量请求参数", + "description": "模型列表与分页信息", "properties": { - "key": { - "type": "string", - "description": "密文变量名称" + "models": { + "type": "array", + "description": "结果模型列表", + "items": { + "$ref": "#/components/schemas/Model" + } + }, + "total_count": { + "type": "integer", + "description": "符合筛选条件的模型总数", + "examples": [ + 43127 + ] + }, + "page_number": { + "type": "integer", + "description": "当前页码", + "examples": [ + 1 + ] + }, + "page_size": { + "type": "integer", + "description": "每页大小", + "examples": [ + 10 + ] } }, "required": [ - "key" + "models", + "total_count", + "page_number", + "page_size" ] }, - "StudioRuntimeResponse": { + "ModelListResponse": { "type": "object", - "description": "Studio 运行时操作响应", + "description": "列出模型的标准响应包裹格式", "properties": { "success": { - "type": "boolean" + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] }, "data": { - "$ref": "#/components/schemas/StudioRuntime" + "$ref": "#/components/schemas/ModelList", + "description": "模型列表数据" }, "request_id": { - "type": "string" + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] } }, "required": [ "success", + "data", "request_id" ] }, - "StudioLogsResponse": { - "type": "object", - "description": "获取 Studio 日志响应", - "properties": { - "success": { - "type": "boolean" + "ModelDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/Model" }, - "data": { + { "type": "object", + "description": "模型详情,包含 README 和当前模型类型", "properties": { - "logs": { - "type": "array", - "items": { - "type": "string" - } + "readme": { + "type": [ + "string", + "null" + ], + "description": "README 内容", + "examples": [ + "hello" + ] }, - "page_num": { - "type": "integer" + "model_category": { + "$ref": "#/components/schemas/ModelCategory" }, - "page_size": { - "type": "integer" + "owner": { + "type": "string" }, - "total_count": { - "type": "integer" + "repo_name": { + "type": "string" }, - "total_page_num": { - "type": "integer" + "visibility": { + "type": "string", + "enum": [ + "public", + "private", + "gated" + ] + }, + "gated_mode": { + "type": "string", + "enum": [ + "auto", + "manual" + ], + "description": "申请制审批方式。仅 gated=true 时返回;auto 表示申请后自动通过,manual 表示需要人工审批。" + }, + "aigc_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AigcModelType" + } + ], + "description": "【AIGC 必填】模型子类型,可选值为 Checkpoint、LoRA、VAE(区分大小写);其他值返回 HTTP 400。" + }, + "base_model_architecture_type": { + "type": "string", + "description": "【AIGC 必填】基础模型架构类型(一级选择)。可选值查询 GET /model/aigc/options", + "examples": [ + "Z_IMAGE_TURBO", + "FLUX_2" + ] + }, + "base_model_architecture_subtype": { + "type": "string", + "description": "【AIGC 必填】基础模型架构子类型(二级选择),需与 base_model_architecture_type 匹配。可选值查询 GET /model/aigc/options", + "examples": [ + "Z_IMAGE_TURBO", + "FLUX2_DEV" + ] + }, + "content_categories": { + "allOf": [ + { + "$ref": "#/components/schemas/AigcContentCategories" + } + ], + "description": "【AIGC 可选】内容类别三级联动" + }, + "primary_subject_area": { + "allOf": [ + { + "type": "string" + } + ], + "description": "【科学 必填】一级学科分类,实际值以 GET /model/science/options 为准,例如 life-sciences" + }, + "secondary_specialization": { + "allOf": [ + { + "type": "string" + } + ], + "description": "【科学 必填】二级专业分类,应与 primary_subject_area 匹配;实际值以 GET /model/science/options 为准,例如 dna" + }, + "cover_image": { + "type": "string", + "description": "【Science 可选】模型封面图;可省略或传空字符串,非空时须为 HTTP(S) URL。", + "format": "uri" + }, + "versions": { + "type": "array", + "description": "仅 AIGC 返回;其他类型不返回专区字段。", + "items": { + "$ref": "#/components/schemas/ModelVersion" + } } } - }, - "request_id": { - "type": "string" } - }, - "required": [ - "success", - "request_id" ] }, - "SuccessResponse": { + "ModelDetailResponse": { "type": "object", - "description": "通用成功响应", + "description": "获取模型详情的标准响应包裹格式", "properties": { "success": { - "type": "boolean" + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] }, - "message": { - "type": "string" + "data": { + "$ref": "#/components/schemas/ModelDetail", + "description": "模型详情数据" }, "request_id": { - "type": "string" + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] } }, "required": [ "success", + "data", "request_id" ] }, - "StudioVisibility": { - "type": "string", - "description": "创空间可见性:\n- public: 代码和体验都公开\n- protected: 体验公开,代码仓库不可见\n- private: 都不公开\n", - "enum": [ - "public", - "protected", - "private" - ] - }, - "HardwareItem": { + "Dataset": { "type": "object", - "description": "硬件配置项。免费资源仅返回基础标识字段;付费资源会返回 ECS 规格、资源明细和价格字段", + "description": "数据集摘要信息", "properties": { - "name": { + "id": { "type": "string", - "description": "OpenAPI hardware 参数值。付费资源格式为 `paid/`", + "description": "数据集仓库名(repo_id,格式为 owner/repo_name)", "examples": [ - "platform/2v-cpu-16g-mem", - "paid/ecs.gn7i-c8g1.2xlarge" + "DAMO_NLP/jd", + "iic/nlp_domain_classification_chinese_testset" ] }, - "resource_type": { - "type": "string", - "description": "资源类型,free 表示平台免费资源,paid 表示使用用户自己的云账号付费部署", - "enum": [ - "free", - "paid" + "display_name": { + "type": [ + "string", + "null" + ], + "description": "数据集展示名称", + "examples": [ + "商品评论情感预测", + "中文文本领域分类测试集" ] }, - "instance_type": { - "type": "string", - "description": "ECS 规格名,仅付费资源返回", + "description": { + "type": [ + "string", + "null" + ], + "description": "数据集简介", "examples": [ - "ecs.gn7i-c8g1.2xlarge" + "", + "..." ] }, - "cpu": { + "file_size": { "type": "integer", - "description": "CPU 核数,仅付费资源返回" + "description": "文件大小(字节)", + "examples": [ + 0 + ] }, - "gpu": { + "downloads": { "type": "integer", - "description": "GPU 数量,仅付费资源返回" + "description": "下载量", + "examples": [ + 53996, + 2401 + ] }, - "memory": { + "likes": { "type": "integer", - "description": "内存(GB),仅付费资源返回" - }, - "gpu_type": { - "type": "string", - "description": "GPU 类型,仅付费资源返回" + "description": "喜欢量", + "examples": [ + 102, + 23 + ] }, - "gpu_memory": { - "type": "integer", - "description": "显存(GB),仅付费资源返回" + "license": { + "type": [ + "string", + "null" + ], + "description": "许可证", + "examples": [ + "Apache License 2.0", + "cc-by-4.0" + ] }, - "supported_sdk_types": { + "tasks": { "type": "array", + "description": "任务列表", "items": { "type": "string" }, - "description": "支持的 SDK 类型" - }, - "has_stock": { - "type": "boolean", - "description": "是否有库存,仅付费资源返回" - }, - "stock": { - "type": "integer", - "description": "库存数量" - }, - "cost_after_discount": { - "type": "number", - "format": "float", - "description": "折后价格,仅付费资源返回" - }, - "original_cost": { - "type": "number", - "format": "float", - "description": "原价,仅付费资源返回" - } - } - }, - "ListHardwareResponse": { - "type": "object", - "description": "查询可用硬件配置响应", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "hardware": { - "type": "array", - "items": { - "$ref": "#/components/schemas/HardwareItem" - } - } - } - }, - "request_id": { - "type": "string" - } - }, - "required": [ - "success", - "request_id" - ] - }, - "SDKVersionItem": { - "type": "object", - "description": "SDK 版本项", - "properties": { - "sdk_type": { - "type": "string", - "description": "SDK 类型", "examples": [ - "gradio" + [ + "text-classification" + ], + [] ] }, - "tag": { + "created_at": { "type": "string", - "description": "版本标签", + "format": "date-time", + "description": "创建时间(ISO 8601 UTC)", "examples": [ - "latest" + "2022-09-28T06:30:31Z", + "2025-11-25T14:30:22Z" ] }, - "version": { + "last_modified": { "type": "string", - "description": "SDK 版本", + "format": "date-time", + "description": "Git 文件或设置的最后修改时间(ISO 8601 UTC)", "examples": [ - "6.2.0" + "2022-10-30T03:36:39Z", + "2026-01-01T00:31:42Z" ] - } - }, - "required": [ - "sdk_type", - "tag", - "version" - ] - }, - "ListSDKVersionsResponse": { - "type": "object", - "description": "查询可用 SDK 版本响应", - "properties": { - "success": { - "type": "boolean" }, - "data": { - "type": "object", - "properties": { - "sdk_versions": { - "type": "array", - "description": "SDK 版本列表。仅 `sdk_type=gradio` 时返回 Gradio 版本列表;其他 SDK 类型返回空数组", - "items": { - "$ref": "#/components/schemas/SDKVersionItem" - } - }, - "total_count": { - "type": "integer", - "format": "int64", - "description": "SDK 版本总数" - } - } + "tags": { + "type": "array", + "description": "标签列表", + "items": { + "type": "string" + }, + "examples": [ + [ + "license:Apache License 2.0", + "task:text-classification" + ], + [ + "license:cc-by-4.0", + "custom_tag:common-crawl" + ] + ] }, - "request_id": { - "type": "string" + "private": { + "type": "boolean", + "description": "是否普通私有数据集;申请制数据集返回 false,请结合 gated 和 visibility 判断访问模式", + "examples": [ + false + ] + }, + "gated": { + "type": "boolean", + "description": "是否申请制数据集,通过申请后可访问", + "examples": [ + false + ] + }, + "login_required": { + "type": "boolean", + "description": "是否登录后才可下载", + "examples": [ + false + ] } }, "required": [ - "success", - "request_id" + "id", + "downloads", + "likes", + "private", + "gated", + "login_required", + "created_at", + "last_modified" ] }, - "BaseImageItem": { + "DatasetList": { "type": "object", - "description": "基础镜像项", + "description": "数据集列表与分页信息", "properties": { - "name": { - "type": "string", - "description": "镜像名称", + "datasets": { + "type": "array", + "description": "结果数据集列表", + "items": { + "$ref": "#/components/schemas/Dataset" + } + }, + "total_count": { + "type": "integer", + "description": "符合筛选条件的数据集总数", "examples": [ - "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0" + 60 ] }, - "tag": { - "type": "string", - "description": "标签", + "page_number": { + "type": "integer", + "description": "当前页码", "examples": [ - "latest" + 1 + ] + }, + "page_size": { + "type": "integer", + "description": "每页大小", + "examples": [ + 10 ] } - } + }, + "required": [ + "datasets", + "total_count", + "page_number", + "page_size" + ] }, - "ListBaseImagesResponse": { + "DatasetListResponse": { "type": "object", - "description": "查询可用基础镜像响应", + "description": "列出数据集的标准响应包裹格式", "properties": { "success": { - "type": "boolean" + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] }, "data": { - "type": "object", - "properties": { - "base_images": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BaseImageItem" - } - } - } + "$ref": "#/components/schemas/DatasetList", + "description": "数据集列表数据" }, "request_id": { - "type": "string" + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] } }, "required": [ "success", + "data", "request_id" ] }, - "AddStudioVariableRequest": { + "DatasetDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/Dataset" + }, + { + "type": "object", + "description": "数据集详情信息", + "properties": { + "readme": { + "type": [ + "string", + "null" + ], + "description": "README 内容", + "examples": [ + "hello" + ] + }, + "owner": { + "type": "string" + }, + "repo_name": { + "type": "string" + }, + "dataset_category": { + "type": "string", + "enum": [ + "base", + "science" + ] + }, + "visibility": { + "type": "string", + "enum": [ + "public", + "private", + "gated" + ] + }, + "gated_mode": { + "type": "string", + "enum": [ + "auto", + "manual" + ], + "description": "申请制审批方式。仅 gated=true 时返回;auto 表示申请后自动通过,manual 表示需要人工审批。" + }, + "login_required": { + "type": "boolean" + }, + "primary_subject_area": { + "type": "string" + }, + "secondary_specialization": { + "type": "string" + }, + "cover_image": { + "type": "string" + } + } + } + ] + }, + "DatasetDetailResponse": { "type": "object", - "description": "添加 Studio 明文变量请求参数(key 和 value 都公开可见,敏感信息请使用密文变量接口)", + "description": "获取数据集详情的标准响应包裹格式", "properties": { - "key": { - "type": "string", - "description": "明文变量名称", - "maxLength": 128, + "success": { + "type": "boolean", + "description": "成功时始终为 true", "examples": [ - "GRADIO_TEMP_DIR" + true ] }, - "value": { + "data": { + "$ref": "#/components/schemas/DatasetDetail", + "description": "数据集详情数据" + }, + "request_id": { "type": "string", - "description": "明文变量值", - "maxLength": 4096, + "description": "请求 ID", "examples": [ - "/tmp/gradio" + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" ] } }, "required": [ - "key", - "value" + "success", + "data", + "request_id" ] }, - "UpdateStudioVariableRequest": { + "McpServerLocale": { "type": "object", - "description": "更新 Studio 明文变量请求参数", + "description": "MCP服务在对应语言环境下的内容", "properties": { - "key": { + "name": { "type": "string", - "description": "明文变量名称", - "maxLength": 128, + "description": "MCP服务在对应语言环境下的服务名称", "examples": [ - "GRADIO_TEMP_DIR" + "Fetch网页内容抓取" ] }, - "value": { + "description": { "type": "string", - "description": "明文变量新值", - "maxLength": 4096, + "description": "MCP服务在对应语言环境下的服务简介", "examples": [ - "/new/path" + "该服务器使大型语言模型能够检索和处理网页内容..." + ] + }, + "readme": { + "type": "string", + "description": "MCP服务介绍(仅详情接口返回)", + "examples": [ + "# Fetch MCP Server..." ] } - }, - "required": [ - "key", - "value" - ] + } }, - "DeleteStudioVariableRequest": { + "McpServerLocales": { "type": "object", - "description": "删除 Studio 明文变量请求参数", + "description": "不同语言环境下的内容展示", "properties": { - "key": { - "type": "string", - "description": "明文变量名称" + "zh": { + "$ref": "#/components/schemas/McpServerLocale", + "description": "中文环境" + }, + "en": { + "$ref": "#/components/schemas/McpServerLocale", + "description": "英文环境" } - }, - "required": [ - "key" - ] + } }, - "StudioVariableItem": { + "McpOperationalUrl": { "type": "object", - "description": "明文变量列表项(返回 key 和 value)", + "description": "MCP服务托管链接信息", "properties": { - "key": { + "id": { "type": "string", - "description": "明文变量名称" + "description": "MCP服务链接命名标识", + "examples": [ + "platform-pool" + ] }, - "value": { + "url": { "type": "string", - "description": "明文变量值" - } - }, - "required": [ - "key", - "value" - ] - }, - "ListStudioVariablesResponse": { - "type": "object", - "description": "获取 Studio 明文变量列表响应", - "properties": { - "success": { - "type": "boolean" - }, - "data": { - "type": "object", - "properties": { - "variables": { - "type": "array", - "items": { - "$ref": "#/components/schemas/StudioVariableItem" - } - } - } + "description": "MCP服务远程连接地址", + "examples": [ + "https://mcp.api-inference.modelscope.net/xxxx/mcp" + ] }, - "request_id": { - "type": "string" - } - }, - "required": [ - "success", - "data", - "request_id" - ] - }, - "SkillLocale": { - "type": "object", - "description": "技能的单语言本地化信息", - "properties": { - "description": { + "transport_type": { "type": "string", - "description": "技能描述的本地化翻译" + "description": "MCP服务链接传输方式", + "examples": [ + "streamable_http", + "sse" + ] }, - "category": { + "auth_required": { + "type": "boolean", + "description": "远程 url 连接时是否需要提供魔搭访问令牌鉴权", + "examples": [ + false + ] + }, + "expiration": { "type": "string", - "description": "技能分类的本地化翻译" - } - } - }, - "SkillLocales": { - "type": "object", - "description": "技能的多语言本地化信息", - "properties": { - "en": { - "$ref": "#/components/schemas/SkillLocale" + "description": "MCP服务链接有效期", + "examples": [ + "2025-10-01 21:00:00" + ] }, - "zh": { - "$ref": "#/components/schemas/SkillLocale" + "accessible": { + "type": "boolean", + "description": "是否有权限访问该MCP服务详情。当所有者设置为非公开或MCP服务被删除时,无权访问。", + "examples": [ + true + ] } } }, - "SkillSummary": { + "McpServerSummary": { "type": "object", - "description": "技能摘要信息", + "description": "MCP服务摘要信息", "properties": { - "_id": { + "id": { "type": "string", - "description": "技能内部唯一标识", + "description": "服务ID。由平台后台收录的MCP,ID 默认为@author/server_name,当被社区用户认领后将会变更为user_name/server_name;由社区用户自发提交的MCP,ID 为user_name/server_name。", "examples": [ - "62+FGAr3b7f4Bblezwx8hQ==" + "@modelcontextprotocol/fetch" ] }, - "id": { + "name": { "type": "string", - "description": "技能 ID,格式为 @author/skill_name 或 owner/skill_name", + "description": "MCP服务在魔搭社区的名称", "examples": [ - "@AMap-Web/amap-lbs-skill" + "Fetch网页内容抓取" ] }, - "display_name": { + "chinese_name": { "type": "string", - "description": "技能展示名称", + "description": "MCP服务在魔搭社区的中文名称", "examples": [ - "高德地图综合服务Skill" + "Fetch网页内容抓取" ] }, "description": { "type": "string", - "description": "技能描述" - }, - "owner": { - "type": "string", - "description": "技能所有者(用户名或组织名)", + "description": "MCP服务简介", "examples": [ - "guoyanlian01" + "该服务器使大型语言模型能够检索和处理网页内容..." ] }, - "license": { + "logo_url": { "type": "string", - "description": "许可证", + "description": "logo图片URL链接", "examples": [ - "MIT License" + "https://resources.modelscope.cn/..." ] }, - "developer": { + "publisher": { "type": "string", - "description": "开发者", + "description": "发布时原始ID", "examples": [ - "AMap-Web" + "@modelcontextprotocol/fetch" ] }, - "source_url": { - "type": "string", - "description": "源码地址" - }, - "category": { - "type": "string", - "description": "技能分类", + "categories": { + "type": "array", + "description": "该MCP服务所属分类", + "items": { + "type": "string" + }, "examples": [ - "developer-tools" + [ + "browser-automation" + ] ] }, "tags": { - "type": [ - "array", - "null" - ], - "description": "标签列表,格式为 `key:value`,key 包括 category / license / developer / custom_tag", + "type": "array", + "description": "该MCP服务的标签列表", "items": { "type": "string" }, "examples": [ [ - "category:developer-tools", - "license:MIT License", - "developer:AMap-Web", - "custom_tag:api-design" + "browser-automation" ] ] }, - "logo_url": { - "type": "string", - "description": "logo 图片 URL" - }, "view_count": { "type": "integer", - "description": "浏览量", - "examples": [ - 13460 - ] - }, - "downloads": { - "type": "integer", - "description": "下载量", + "description": "该MCP服务的累计访问量", "examples": [ - 1203 + 30667 ] }, "locales": { - "$ref": "#/components/schemas/SkillLocales" - }, - "private": { - "type": "boolean", - "description": "是否为私有技能", - "examples": [ - false - ] - }, - "custom_tag": { - "type": [ - "array", - "null" - ], - "description": "自定义标签列表(无标签时为 null)", - "items": { - "type": "string" - }, - "examples": [ - [ - "api-design", - "general-tools" - ], - null - ] + "$ref": "#/components/schemas/McpServerLocales" } - } + }, + "required": [ + "id" + ] }, - "SkillDetail": { + "McpServerDetail": { "type": "object", - "description": "技能详细信息,包含安装命令", + "description": "MCP服务详细信息", "allOf": [ { - "$ref": "#/components/schemas/SkillSummary" + "$ref": "#/components/schemas/McpServerSummary" }, { "type": "object", "properties": { - "install_command": { - "type": "array", - "description": "安装命令列表", - "items": { - "type": "string" - }, + "author": { + "type": "string", + "description": "MCP服务开发者", "examples": [ - [ - "npx skills add https://modelscope.cn/skills/@AMap-Web/amap-lbs-skill", - "curl -fsSL https://modelscope.cn/skills/install.sh | bash -s -- @AMap-Web/amap-lbs-skill" - ] + "modelcontextprotocol" ] - } - } - } - ] - }, - "SkillListResponse": { - "type": "object", - "description": "技能列表响应", - "properties": { - "success": { - "type": "boolean", - "examples": [ - true - ] - }, - "request_id": { - "type": "string", - "examples": [ - "c5bca202-aa4f-478b-b7e2-c568160583cb" - ] - }, - "data": { - "type": "object", - "properties": { - "skills": { - "type": "array", - "description": "技能列表", - "items": { - "$ref": "#/components/schemas/SkillSummary" - } }, - "total": { - "type": "integer", - "format": "int64", - "description": "总记录数", + "owner": { + "type": "string", + "description": "MCP服务归属的魔搭用户/组织ID", "examples": [ - 20 + "" ] }, - "page_number": { - "type": "integer", - "description": "当前页码", + "readme": { + "type": "string", + "description": "MCP服务介绍", "examples": [ - 1 + "# Fetch MCP Server..." ] }, - "page_size": { + "source_url": { + "type": "string", + "description": "MCP服务来源项目主页地址", + "examples": [ + "https://github.com/modelcontextprotocol/..." + ] + }, + "github_stars": { "type": "integer", - "description": "每页条数", + "description": "该 MCP 服务对应 github 原始仓库的 stars 数量", "examples": [ - 20 + 1234 + ] + }, + "is_hosted": { + "type": "boolean", + "description": "该MCP服务是否支持托管部署", + "examples": [ + true + ] + }, + "is_verified": { + "type": "boolean", + "description": "托管MCP服务是否经过平台验证测试", + "examples": [ + true ] + }, + "env_schema": { + "type": "object", + "description": "该服务连接部署的环境变量配置", + "additionalProperties": true + }, + "server_config": { + "type": "array", + "description": "MCP服务配置信息", + "items": {} + }, + "operational_urls": { + "type": "array", + "description": "当get_operational_url=True且当前用户已连接该 MCP 服务时则返回,否则为空", + "items": { + "$ref": "#/components/schemas/McpOperationalUrl" + } } } } - } + ] }, - "SkillDetailResponse": { + "McpServerOperational": { "type": "object", - "description": "技能详情响应", + "description": "用户托管的MCP服务信息", + "allOf": [ + { + "$ref": "#/components/schemas/McpServerSummary" + }, + { + "type": "object", + "properties": { + "operational_urls": { + "type": "array", + "description": "MCP服务托管链接列表", + "items": { + "$ref": "#/components/schemas/McpOperationalUrl" + } + } + } + } + ] + }, + "McpServerListResponse": { + "type": "object", + "description": "MCP服务列表响应", "properties": { "success": { "type": "boolean", + "description": "成功时始终为 true", "examples": [ true ] }, - "request_id": { + "data": { + "$ref": "#/components/schemas/McpServerList", + "description": "MCP服务列表数据" + }, + "message": { "type": "string", + "description": "响应消息", "examples": [ - "105c898f-fd6d-437d-aa0b-9006036782da" + "success" ] }, - "data": { - "$ref": "#/components/schemas/SkillDetail" + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "c55709d2-4e09-4bd8-be84-d8518bc46363" + ] } - } + }, + "required": [ + "success", + "request_id" + ] }, - "CreateSkillRequest": { + "McpServerOperationalListResponse": { "type": "object", - "description": "创建 Skill 请求参数", + "description": "用户托管MCP服务列表响应", "properties": { - "skill_name": { - "type": "string", - "description": "英文名称,创建后不可更改,仅允许小写字母、数字和连字符", - "pattern": "^[a-z0-9-]+$", - "maxLength": 64, + "success": { + "type": "boolean", + "description": "成功时始终为 true", "examples": [ - "my-awesome-skill" + true ] }, - "owner": { - "type": "string", - "description": "所有者(用户名或组织名)", - "examples": [ - "iSolver" - ] + "data": { + "$ref": "#/components/schemas/McpServerOperationalList", + "description": "用户托管MCP服务列表数据" }, - "display_name": { + "message": { "type": "string", - "description": "展示名称", - "maxLength": 128, + "description": "响应消息", "examples": [ - "我的技能" + "success" ] }, - "source_url": { + "request_id": { "type": "string", - "format": "uri", - "description": "来源地址", + "description": "请求 ID", "examples": [ - "https://github.com/example/my-skill" + "c55709d2-4e09-4bd8-be84-d8518bc46363" ] - }, - "private": { + } + }, + "required": [ + "success", + "request_id" + ] + }, + "McpServerDetailResponse": { + "type": "object", + "description": "MCP服务详情响应", + "properties": { + "success": { "type": "boolean", - "description": "是否私有,默认 false(公开)", - "default": false, + "description": "成功时始终为 true", "examples": [ - false + true ] }, - "description": { + "data": { + "$ref": "#/components/schemas/McpServerDetail", + "description": "MCP服务详情数据" + }, + "message": { "type": "string", - "description": "描述", - "maxLength": 2000, + "description": "响应消息", "examples": [ - "这是一个示例技能" + "success" ] }, - "license": { + "request_id": { "type": "string", - "description": "开源协议证书,不填默认 Apache-2.0", - "default": "Apache-2.0", + "description": "请求 ID", "examples": [ - "Apache-2.0" + "c55709d2-4e09-4bd8-be84-d8518bc46363" ] - }, - "category": { - "allOf": [ - { - "$ref": "#/components/schemas/SkillCategory" - } - ], - "description": "Skill 类型/分类", + } + }, + "required": [ + "success", + "request_id" + ] + }, + "McpDeployResponse": { + "type": "object", + "description": "MCP服务部署响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", "examples": [ - "developer-tools" + true ] }, - "tags": { - "type": "array", - "description": "自定义标签", - "items": { - "type": "string" - }, - "examples": [ - [ - "Skill Creation" - ] - ] + "data": { + "$ref": "#/components/schemas/McpOperationalUrl", + "description": "部署后的MCP服务链接信息" }, - "logo_url": { + "message": { "type": "string", - "format": "uri", - "description": "Skill 图标 URL", + "description": "响应消息", "examples": [ - "https://example.com/logo.png" + "success" ] }, - "skill_file": { + "request_id": { "type": "string", - "description": "Skill 项目文件 ID(通过 POST /files/upload 上传 zip 包后获取)。\nzip 包要求:\n- 最大 5MB\n- zip 根目录下必须包含且仅包含 1 个 SKILL.md 文件\n- 可包含子目录及其他辅助文件(如 prompts/、examples/ 等)\n- SKILL.md 必须包含 YAML front-matter(name、version、description)\n", + "description": "请求 ID", "examples": [ - "8c378570-8991-431b-a82c-96f3d0b4f0f4" + "c55709d2-4e09-4bd8-be84-d8518bc46363" ] } }, "required": [ - "skill_name", - "owner", - "license", - "category", - "skill_file" + "success", + "request_id" ] }, - "CreateSkillResponse": { + "McpUndeployResponse": { "type": "object", - "description": "创建 Skill 响应", + "description": "MCP服务解除部署响应", "properties": { "success": { "type": "boolean", + "description": "成功时始终为 true", "examples": [ true ] }, - "request_id": { + "message": { "type": "string", + "description": "响应消息", "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" + "success" ] }, - "data": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Skill ID (owner/skill_name)", - "examples": [ - "iSolver/my-awesome-skill" - ] - }, - "name": { - "type": "string", - "description": "Skill 英文名称", - "examples": [ - "my-awesome-skill" - ] - }, - "display_name": { - "type": "string", - "description": "展示名称", - "examples": [ - "我的技能" - ] - }, - "owner": { - "type": "string", - "description": "所有者", - "examples": [ - "iSolver" - ] - }, - "url": { - "type": "string", - "description": "Skill 页面 URL", - "examples": [ - "https://modelscope.cn/skills/iSolver/my-awesome-skill" - ] - } - } + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "c55709d2-4e09-4bd8-be84-d8518bc46363" + ] } }, "required": [ @@ -5686,110 +6293,126 @@ "request_id" ] }, - "UpdateSkillSettingsRequest": { + "StudioHardware": { + "type": "string", + "description": "硬件配置,修改后需重新部署才能生效。\n可用值不固定,请通过 `GET /studios/hardware` 接口查询当前可用的硬件配置列表。\n免费资源常见格式为 `platform/...`、`xgpu/...`、`amd/...`;付费资源格式为 `paid/`,例如 `paid/ecs.gn7i-c8g1.2xlarge`。\n", + "examples": [ + "platform/2v-cpu-16g-mem", + "paid/ecs.gn7i-c8g1.2xlarge" + ] + }, + "StudioSDKType": { + "type": "string", + "description": "SDK 类型,修改后需重新部署才能生效", + "enum": [ + "gradio", + "streamlit", + "docker", + "static" + ] + }, + "StudioSDKVersion": { + "type": "string", + "description": "SDK 版本,仅对 Gradio 类型生效,默认最新版,建议选用最新版本,修改后需重新部署才能生效。\n可用版本会随平台更新,请通过 `GET /studios/sdk-versions?sdk_type=gradio` 查询当前可用的 Gradio 版本列表。\n", + "examples": [ + "6.2.0" + ] + }, + "StudioBaseImage": { + "type": "string", + "description": "基础镜像,仅 Docker 类型不支持,建议选用最新版本,修改后需重新部署才能生效。\n可用值不固定,请通过 `GET /studios/base-images` 接口查询当前可用的基础镜像列表。\n", + "examples": [ + "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0" + ] + }, + "CreateStudioRequest": { "type": "object", - "description": "更新 Skill 设置请求参数(传入哪个字段修改哪个字段,不传的字段不修改)", + "description": "创建 Studio 请求参数", "properties": { + "repo_name": { + "type": "string", + "description": "仓库名称", + "maxLength": 64, + "examples": [ + "my-demo-app" + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "username" + ] + }, "display_name": { "type": "string", - "description": "展示名称", + "description": "显示名称", "maxLength": 128, "examples": [ - "新的展示名称" + "我的演示应用" ] }, - "source_url": { + "license": { "type": "string", - "format": "uri", - "description": "来源地址", + "description": "许可证,默认 apache-2.0", + "default": "apache-2.0", "examples": [ - "https://github.com/example/my-skill" + "apache-2.0" ] }, + "visibility": { + "$ref": "#/components/schemas/StudioVisibility" + }, "private": { "type": "boolean", - "description": "是否私有", - "examples": [ - true - ] + "description": "Deprecated: 请使用 visibility 字段", + "deprecated": true, + "default": false }, "description": { "type": "string", "description": "描述", "maxLength": 2000, "examples": [ - "更新后的技能描述" + "这是一个演示应用" ] }, - "license": { + "cover_image": { "type": "string", - "description": "开源协议证书", - "examples": [ - "Apache-2.0" - ] + "description": "封面图 URL,为空时使用平台默认图" }, - "category": { + "sdk_type": { "allOf": [ { - "$ref": "#/components/schemas/SkillCategory" + "$ref": "#/components/schemas/StudioSDKType" } ], - "description": "Skill 类型/分类", - "examples": [ - "developer-tools" - ] + "default": "gradio" }, - "tags": { - "type": "array", - "description": "自定义标签(传入时整体覆盖原标签列表)", - "items": { - "type": "string" - }, - "examples": [ - [ - "Image/Video Gen", - "Media Processing" - ] - ] + "sdk_version": { + "$ref": "#/components/schemas/StudioSDKVersion" }, - "logo_url": { - "type": "string", - "format": "uri", - "description": "Skill 图标 URL(传空字符串恢复为平台默认图标)", - "examples": [ - "https://example.com/new-logo.png" - ] + "base_image": { + "$ref": "#/components/schemas/StudioBaseImage" }, - "skill_file": { - "type": "string", - "description": "Skill 项目文件 ID(通过 POST /files/upload 上传 zip 包后获取,传入时整体覆盖原文件,不传则保留原文件不变)。\nzip 包要求:\n- 最大 5MB\n- zip 根目录下必须包含且仅包含 1 个 SKILL.md 文件\n- 可包含子目录及其他辅助文件(如 prompts/、examples/ 等)\n- SKILL.md 必须包含 YAML front-matter(name、version、description)\n", - "examples": [ - "new-file-id-xxx" - ] + "hardware": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioHardware" + } + ], + "default": "platform/2v-cpu-16g-mem", + "description": "硬件配置。未传时使用默认免费硬件配置" } - } - }, - "SkillCategory": { - "type": "string", - "description": "Skill 类型/分类枚举", - "enum": [ - "skill-management", - "developer-tools", - "marketing-seo", - "frontend-development", - "ai-media", - "code-quality-testing", - "mobile-development", - "cloud-devops", - "other" - ], - "examples": [ - "developer-tools" + }, + "required": [ + "repo_name", + "owner" ] }, - "GetBalanceResponse": { + "CreateStudioResponse": { "type": "object", - "description": "余额查询响应", + "description": "创建 Studio 响应", "properties": { "success": { "type": "boolean", @@ -5800,34 +6423,45 @@ "data": { "type": "object", "properties": { - "total_balance": { - "type": "number", - "description": "总额度(可用额度 + 预扣额度)", + "id": { + "type": "string", + "description": "Studio ID (owner/repo_name)", "examples": [ - 1500 + "username/my-demo-app" ] }, - "available_balance": { - "type": "number", - "description": "可用额度", + "repo_name": { + "type": "string", + "description": "仓库名称", "examples": [ - 1300 + "my-demo-app" ] }, - "frozen_amount": { - "type": "number", - "description": "预扣额度(进行中任务未返回结果时预扣减)", + "display_name": { + "type": "string", + "description": "显示名称", "examples": [ - 200 + "我的演示应用" + ] + }, + "owner": { + "type": "string", + "description": "所有者", + "examples": [ + "username" + ] + }, + "url": { + "type": "string", + "description": "Studio URL", + "examples": [ + "https://modelscope.cn/studios/username/my-demo-app" ] } } }, "request_id": { - "type": "string", - "examples": [ - "c55709d2-4e09-4bd8-be84-d8518bc46363" - ] + "type": "string" } }, "required": [ @@ -5835,1145 +6469,990 @@ "request_id" ] }, - "UploadFileRequest": { + "StudioConfig": { "type": "object", - "description": "上传文件请求参数(multipart/form-data)", + "description": "Studio 运行时配置", "properties": { - "file": { - "type": "string", - "format": "binary", - "description": "上传的文件,最大 5MB" + "hardware": { + "$ref": "#/components/schemas/StudioHardware" }, - "filename": { - "type": [ - "string", - "null" + "base_image": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioBaseImage" + } ], - "description": "自定义文件名(选填),支持多级目录路径(如 images/sub/logo.png);不提供则使用上传文件的原始文件名。不允许绝对路径或目录穿越(..)。", - "examples": [ - "images/sub/logo.png" - ] + "description": "基础镜像(Docker 类型不返回)" + }, + "sdk_type": { + "$ref": "#/components/schemas/StudioSDKType" + }, + "sdk_version": { + "type": "string", + "description": "SDK 版本(仅 gradio 类型返回)" } - }, - "required": [ - "file" - ] + } }, - "UploadFileResponse": { + "StudioRuntime": { "type": "object", - "description": "上传文件响应", + "description": "Studio 运行时信息", "properties": { - "success": { - "type": "boolean", - "examples": [ - true + "status": { + "type": "string", + "description": "运行状态", + "enum": [ + "Initialized", + "Building", + "BuildFailed", + "Deploying", + "DeployFailed", + "Running", + "Stopping", + "Stopped", + "Duplicating", + "Sleeping" ] }, - "request_id": { + "active_config": { + "description": "当前实际运行中的配置", + "$ref": "#/components/schemas/StudioConfig" + }, + "created_at": { "type": "string", - "examples": [ - "99ee2083-63e0-46dc-b393-623c22645078" - ] + "description": "部署时间" + }, + "error_message": { + "type": "string", + "description": "失败信息(仅在错误状态时返回)" + } + } + }, + "GetStudioResponse": { + "type": "object", + "description": "获取 Studio 详情响应", + "properties": { + "success": { + "type": "boolean" }, "data": { "type": "object", "properties": { "id": { "type": "string", - "description": "文件 ID,可在其他接口中作为入参引用该文件", - "examples": [ - "8c378570-8991-431b-a82c-96f3d0b4f0f4" - ] + "description": "Studio ID (owner/repo_name)" + }, + "repo_name": { + "type": "string", + "description": "仓库名称" + }, + "display_name": { + "type": "string", + "description": "显示名称" + }, + "owner": { + "type": "string", + "description": "所有者" + }, + "description": { + "type": "string", + "description": "描述" + }, + "cover_image": { + "type": "string", + "description": "封面图 URL" + }, + "likes": { + "type": "integer", + "description": "喜欢数" + }, + "view_count": { + "type": "integer", + "description": "访问量" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "visibility": { + "$ref": "#/components/schemas/StudioVisibility" + }, + "private": { + "type": "boolean", + "description": "Deprecated: 请使用 visibility 字段", + "deprecated": true + }, + "last_modified": { + "type": "string", + "description": "最后修改时间" + }, + "sdk_type": { + "$ref": "#/components/schemas/StudioSDKType" + }, + "sdk_version": { + "$ref": "#/components/schemas/StudioSDKVersion" + }, + "hardware": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioHardware" + } + ], + "description": "期望的硬件配置(数据库中保存的值)" + }, + "base_image": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioBaseImage" + } + ], + "description": "期望的基础镜像(Docker 类型不返回)" + }, + "license": { + "type": "string", + "description": "许可证" + }, + "host": { + "type": "string", + "description": "访问地址" + }, + "mcp_support": { + "type": "boolean", + "description": "是否支持 MCP" + }, + "runtime": { + "$ref": "#/components/schemas/StudioRuntime" } - }, - "required": [ - "id" - ] + } + }, + "request_id": { + "type": "string" } }, "required": [ "success", - "request_id", - "data" + "request_id" ] }, - "JWK": { + "UpdateStudioSettingsRequest": { "type": "object", - "description": "JSON Web Key(仅用于 Ed25519 公钥)", - "required": [ - "kty", - "crv", - "x", - "kid" - ], + "description": "更新 Studio 设置请求参数(传入哪个字段修改哪个字段)", "properties": { - "kty": { + "display_name": { "type": "string", - "description": "密钥类型,固定 OKP", - "enum": [ - "OKP" - ], - "examples": [ - "OKP" - ] + "description": "显示名称", + "maxLength": 128 }, - "crv": { + "license": { "type": "string", - "description": "椭圆曲线类型,固定 Ed25519", - "enum": [ - "Ed25519" - ], - "examples": [ - "Ed25519" - ] + "description": "许可证" }, - "x": { - "type": "string", - "description": "公钥 base64url 编码(无 padding)", - "examples": [ - "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs" - ] + "visibility": { + "$ref": "#/components/schemas/StudioVisibility" }, - "kid": { - "type": "string", - "description": "密钥 ID", - "examples": [ - "agent-key-001" - ] + "private": { + "type": "boolean", + "description": "Deprecated: 请使用 visibility 字段", + "deprecated": true }, - "alg": { + "description": { "type": "string", - "description": "签名算法", - "examples": [ - "EdDSA" - ] + "description": "描述", + "maxLength": 2000 }, - "use": { + "cover_image": { "type": "string", - "description": "用途", - "examples": [ - "sig" - ] + "description": "封面图 URL" + }, + "sdk_type": { + "$ref": "#/components/schemas/StudioSDKType" + }, + "sdk_version": { + "$ref": "#/components/schemas/StudioSDKVersion" + }, + "base_image": { + "$ref": "#/components/schemas/StudioBaseImage" + }, + "hardware": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioHardware" + } + ], + "description": "硬件配置,修改后需重新部署才能生效" } } }, - "Principal": { + "AddStudioSecretRequest": { "type": "object", - "description": "Agent 归属主体", + "description": "添加 Studio 密文变量请求参数", "properties": { - "type": { + "key": { "type": "string", - "description": "主体类型", - "enum": [ - "user", - "org" - ], + "description": "密文变量名称", + "maxLength": 128, "examples": [ - "user" + "API_KEY" ] }, - "id": { + "value": { "type": "string", - "description": "主体 ID", + "description": "密文变量值(添加后不会公开展示)", + "maxLength": 4096, "examples": [ - "alice" + "sk-xxxxxxxxxxxx" + ] + }, + "description": { + "type": "string", + "description": "密文变量描述(用途说明),非必填", + "maxLength": 512, + "examples": [ + "推理服务使用的访问密钥" ] } - } - }, - "AgentIdentityStatus": { - "type": "string", - "description": "Agent 状态", - "enum": [ - "active", - "paused" - ], - "examples": [ - "active" + }, + "required": [ + "key", + "value" ] }, - "CreateAgentIdentityRequest": { + "UpdateStudioSecretRequest": { "type": "object", - "description": "创建 Agent 身份请求", - "required": [ - "agent_name", - "public_key" - ], + "description": "更新 Studio 密文变量请求参数", "properties": { - "agent_name": { + "key": { "type": "string", - "description": "Agent 名称(最长 128 字符)", + "description": "密文变量名称", "maxLength": 128, "examples": [ - "my-agent" + "API_KEY" ] }, - "description": { - "type": "string", - "description": "Agent 描述(最长 2000 字符)", - "maxLength": 2000 - }, - "public_key": { - "$ref": "#/components/schemas/JWK" - }, - "key_alg_type": { + "value": { "type": "string", - "description": "密钥算法类型,固定 Ed25519", - "enum": [ - "Ed25519" - ], + "description": "密文变量新值", + "maxLength": 4096, "examples": [ - "Ed25519" + "sk-xxxxxxxxxxxx" ] }, - "token_expire_time": { - "type": "integer", - "description": "Token 过期时间(秒),可选值 300/600/1800/3600", - "enum": [ - 300, - 600, - 1800, - 3600 - ], + "description": { + "type": "string", + "description": "密文变量描述(用途说明),非必填;更新时不传则保留原有描述", + "maxLength": 512, "examples": [ - 3600 + "推理服务使用的访问密钥" ] } - } + }, + "required": [ + "key", + "value" + ] }, - "UpdateAgentIdentityRequest": { + "StudioSecretItem": { "type": "object", - "description": "更新 Agent 身份设置请求", + "description": "密文变量列表项(仅返回 key,不返回 value)", "properties": { - "agent_name": { + "key": { "type": "string", - "maxLength": 128 + "description": "密文变量名称" }, "description": { "type": "string", - "maxLength": 2000 - }, - "token_expire_time": { - "type": "integer", - "enum": [ - 300, - 600, - 1800, - 3600 - ] + "description": "密文变量描述(用途说明),未填写时不返回", + "maxLength": 512 } - } + }, + "required": [ + "key" + ] }, - "ResetKeyPairRequest": { + "ListStudioSecretsResponse": { "type": "object", - "description": "重置密钥对请求(客户端本地生成新密钥后上传公钥)", - "required": [ - "public_key" - ], + "description": "获取 Studio 密文变量列表响应", "properties": { - "public_key": { - "$ref": "#/components/schemas/JWK" + "success": { + "type": "boolean" }, - "key_alg_type": { - "type": "string", - "enum": [ - "Ed25519" - ], - "examples": [ - "Ed25519" - ] + "data": { + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StudioSecretItem" + } + } + } + }, + "request_id": { + "type": "string" } - } - }, - "PauseAgentRequest": { - "type": "object", - "description": "启用/停用 Agent 请求", + }, "required": [ - "paused" - ], - "properties": { - "paused": { - "type": "boolean", - "description": "true 表示停用,false 表示启用", - "examples": [ - true - ] - } - } + "success", + "data", + "request_id" + ] }, - "AgentIdentity": { + "DeleteStudioSecretRequest": { "type": "object", - "description": "Agent 身份详情", + "description": "删除 Studio 密文变量请求参数", "properties": { - "agent_id": { - "type": "string", - "description": "Agent 唯一 ID", - "examples": [ - "agent_id:modelscope:agent_1234567890ab" - ] - }, - "agent_name": { - "type": "string", - "examples": [ - "my-agent" - ] - }, - "description": { - "type": "string" - }, - "token_expire_time": { - "type": "integer", - "examples": [ - 3600 - ] - }, - "principal": { - "$ref": "#/components/schemas/Principal" - }, - "kid": { - "type": "string", - "description": "当前密钥 ID", - "examples": [ - "agent-key-001" - ] - }, - "public_key": { - "$ref": "#/components/schemas/JWK" - }, - "status": { - "$ref": "#/components/schemas/AgentIdentityStatus" - }, - "create_time": { - "type": "string", - "description": "创建时间 (ISO 8601)", - "examples": [ - "2026-06-03T10:00:00Z" - ] - }, - "update_time": { + "key": { "type": "string", - "description": "更新时间 (ISO 8601)", - "examples": [ - "2026-06-03T10:30:00Z" - ] + "description": "密文变量名称" } - } + }, + "required": [ + "key" + ] }, - "AgentIdentityListItem": { + "StudioRuntimeResponse": { "type": "object", - "description": "Agent 身份列表项", + "description": "Studio 运行时操作响应", "properties": { - "agent_id": { - "type": "string" - }, - "agent_name": { - "type": "string" - }, - "kid": { - "type": "string" - }, - "status": { - "$ref": "#/components/schemas/AgentIdentityStatus" + "success": { + "type": "boolean" }, - "token_expire_time": { - "type": "integer" + "data": { + "$ref": "#/components/schemas/StudioRuntime" }, - "create_time": { + "request_id": { "type": "string" } - } + }, + "required": [ + "success", + "request_id" + ] }, - "ListAgentIdentitiesData": { + "StudioLogsResponse": { "type": "object", - "description": "Agent 身份列表数据", + "description": "获取 Studio 日志响应", "properties": { - "agent_identities": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AgentIdentityListItem" - } - }, - "total_count": { - "type": "integer", - "format": "int64" + "success": { + "type": "boolean" }, - "page_number": { - "type": "integer" + "data": { + "type": "object", + "properties": { + "logs": { + "type": "array", + "items": { + "type": "string" + } + }, + "page_num": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total_count": { + "type": "integer" + }, + "total_page_num": { + "type": "integer" + } + } }, - "page_size": { - "type": "integer" + "request_id": { + "type": "string" } - } + }, + "required": [ + "success", + "request_id" + ] }, - "ResetKeyPairData": { + "SuccessResponse": { "type": "object", - "description": "重置密钥对返回数据", + "description": "通用成功响应", "properties": { - "agent_id": { - "type": "string" + "success": { + "type": "boolean" }, - "kid": { + "message": { "type": "string" }, - "public_key": { - "$ref": "#/components/schemas/JWK" + "request_id": { + "type": "string" } - } + }, + "required": [ + "success", + "request_id" + ] }, - "AgentIdentityResponse": { + "StudioVisibility": { + "type": "string", + "description": "创空间可见性:\n- public: 代码和体验都公开\n- protected: 体验公开,代码仓库不可见\n- private: 都不公开\n", + "enum": [ + "public", + "protected", + "private" + ] + }, + "HardwareItem": { "type": "object", - "description": "Agent 身份详情响应", + "description": "硬件配置项。免费资源仅返回基础标识字段;付费资源会返回 ECS 规格、资源明细和价格字段", "properties": { - "success": { - "type": "boolean", + "name": { + "type": "string", + "description": "OpenAPI hardware 参数值。付费资源格式为 `paid/`", "examples": [ - true + "platform/2v-cpu-16g-mem", + "paid/ecs.gn7i-c8g1.2xlarge" ] }, - "data": { - "$ref": "#/components/schemas/AgentIdentity" + "resource_type": { + "type": "string", + "description": "资源类型,free 表示平台免费资源,paid 表示使用用户自己的云账号付费部署", + "enum": [ + "free", + "paid" + ] }, - "message": { + "instance_type": { "type": "string", + "description": "ECS 规格名,仅付费资源返回", "examples": [ - "success" + "ecs.gn7i-c8g1.2xlarge" ] }, - "request_id": { - "type": "string" - } - } - }, - "ListAgentIdentitiesResponse": { - "type": "object", - "description": "Agent 身份列表响应", - "properties": { - "success": { - "type": "boolean" + "cpu": { + "type": "integer", + "description": "CPU 核数,仅付费资源返回" }, - "data": { - "$ref": "#/components/schemas/ListAgentIdentitiesData" + "gpu": { + "type": "integer", + "description": "GPU 数量,仅付费资源返回" }, - "message": { - "type": "string" + "memory": { + "type": "integer", + "description": "内存(GB),仅付费资源返回" }, - "request_id": { - "type": "string" - } - } - }, - "ResetKeyPairResponse": { - "type": "object", - "description": "重置密钥对响应", - "properties": { - "success": { - "type": "boolean" + "gpu_type": { + "type": "string", + "description": "GPU 类型,仅付费资源返回" }, - "data": { - "$ref": "#/components/schemas/ResetKeyPairData" + "gpu_memory": { + "type": "integer", + "description": "显存(GB),仅付费资源返回" }, - "message": { - "type": "string" + "supported_sdk_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "支持的 SDK 类型" }, - "request_id": { - "type": "string" - } - } - }, - "DeleteAgentIdentityResponse": { - "type": "object", - "description": "删除 Agent 身份响应", - "properties": { - "success": { - "type": "boolean" + "has_stock": { + "type": "boolean", + "description": "是否有库存,仅付费资源返回" }, - "message": { - "type": "string" + "stock": { + "type": "integer", + "description": "库存数量" }, - "request_id": { - "type": "string" + "cost_after_discount": { + "type": "number", + "format": "float", + "description": "折后价格,仅付费资源返回" + }, + "original_cost": { + "type": "number", + "format": "float", + "description": "原价,仅付费资源返回" } } }, - "PauseAgentResponse": { + "ListHardwareResponse": { "type": "object", - "description": "启用/停用 Agent 响应", + "description": "查询可用硬件配置响应", "properties": { "success": { "type": "boolean" }, - "message": { - "type": "string" + "data": { + "type": "object", + "properties": { + "hardware": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HardwareItem" + } + } + } }, "request_id": { "type": "string" } - } + }, + "required": [ + "success", + "request_id" + ] }, - "TokenSignRequest": { + "SDKVersionItem": { "type": "object", - "description": "Token 签发请求(Agent 用私钥签名 message=`agent_id|kid|audience|timestamp` 后提交)", - "required": [ - "agent_id", - "kid", - "audience", - "timestamp", - "signature" - ], + "description": "SDK 版本项", "properties": { - "agent_id": { - "type": "string", - "examples": [ - "agent_id:modelscope:agent_1234567890ab" - ] - }, - "kid": { + "sdk_type": { "type": "string", + "description": "SDK 类型", "examples": [ - "agent-key-001" + "gradio" ] }, - "audience": { + "tag": { "type": "string", - "description": "目标 Hub 应用的 client_id", - "examples": [ - "hub-app-abcdef" - ] - }, - "timestamp": { - "type": "integer", - "format": "int64", - "description": "签名时间戳(Unix 秒),需在服务器时间 ±5 分钟之内", + "description": "版本标签", "examples": [ - 1717410000 + "latest" ] }, - "signature": { + "version": { "type": "string", - "description": "Ed25519 签名 base64url 编码(无 padding)", + "description": "SDK 版本", "examples": [ - "MEUCIQDxxx..." + "6.2.0" ] } - } + }, + "required": [ + "sdk_type", + "tag", + "version" + ] }, - "TokenSignData": { + "ListSDKVersionsResponse": { "type": "object", - "description": "Token 签发返回数据", - "properties": { - "access_token": { - "type": "string", - "description": "签发的 JWT", - "examples": [ - "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0..." - ] - }, - "token_type": { - "type": "string", - "enum": [ - "Bearer" - ], - "examples": [ - "Bearer" - ] - }, - "expire_at": { - "type": "integer", - "format": "int64", - "description": "Token 失效时间(Unix 时间戳,单位秒)", - "examples": [ - 1717413600 - ] - }, - "jti": { - "type": "string", - "description": "JWT ID", - "examples": [ - "jti-1234567890" - ] - } - } - }, - "TokenSignResponse": { - "type": "object", - "description": "Token 签发响应", + "description": "查询可用 SDK 版本响应", "properties": { "success": { "type": "boolean" }, "data": { - "$ref": "#/components/schemas/TokenSignData" - }, - "message": { - "type": "string" + "type": "object", + "properties": { + "sdk_versions": { + "type": "array", + "description": "SDK 版本列表。仅 `sdk_type=gradio` 时返回 Gradio 版本列表;其他 SDK 类型返回空数组", + "items": { + "$ref": "#/components/schemas/SDKVersionItem" + } + }, + "total_count": { + "type": "integer", + "format": "int64", + "description": "SDK 版本总数" + } + } }, "request_id": { "type": "string" } - } + }, + "required": [ + "success", + "request_id" + ] }, - "TokenRecordItem": { + "BaseImageItem": { "type": "object", - "description": "Token 签发记录项", + "description": "基础镜像项", "properties": { - "token_id": { - "type": "string" - }, - "audience": { - "type": "string", - "description": "目标 Hub 的 client_id" - }, - "issued_at": { - "type": "string", - "description": "签发时间 (ISO 8601)" - }, - "expire_at": { - "type": "string", - "description": "过期时间 (ISO 8601)" - }, - "status": { + "name": { "type": "string", - "enum": [ - "active", - "expired", - "revoked" + "description": "镜像名称", + "examples": [ + "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0" ] }, - "jwt": { + "tag": { "type": "string", - "description": "JWT 字符串" - } - } - }, - "ListTokenRecordsData": { - "type": "object", - "description": "Token 签发记录列表数据", - "properties": { - "token_records": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TokenRecordItem" - } - }, - "total_count": { - "type": "integer", - "format": "int64" - }, - "page_number": { - "type": "integer" - }, - "page_size": { - "type": "integer" + "description": "标签", + "examples": [ + "latest" + ] } } }, - "ListTokenRecordsResponse": { + "ListBaseImagesResponse": { "type": "object", - "description": "Token 签发记录列表响应", + "description": "查询可用基础镜像响应", "properties": { "success": { "type": "boolean" }, "data": { - "$ref": "#/components/schemas/ListTokenRecordsData" - }, - "message": { - "type": "string" + "type": "object", + "properties": { + "base_images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseImageItem" + } + } + } }, "request_id": { "type": "string" } - } + }, + "required": [ + "success", + "request_id" + ] }, - "AgentIdConfiguration": { + "AddStudioVariableRequest": { "type": "object", - "description": "Agent IDP OIDC Discovery 配置", + "description": "添加 Studio 明文变量请求参数(key 和 value 都公开可见,敏感信息请使用密文变量接口)", "properties": { - "issuer": { + "key": { "type": "string", + "description": "明文变量名称", + "maxLength": 128, "examples": [ - "https://modelscope.cn" + "GRADIO_TEMP_DIR" ] }, - "token_endpoint": { + "value": { "type": "string", + "description": "明文变量值", + "maxLength": 4096, "examples": [ - "https://modelscope.cn/openapi/v1/agent_id/token" + "/tmp/gradio" ] }, - "jwks_uri": { + "description": { "type": "string", + "description": "明文变量描述(用途说明),非必填", + "maxLength": 512, "examples": [ - "https://modelscope.cn/openapi/v1/agent_id/.well-known/agentid-jwks" + "Gradio 运行时临时目录" ] - }, - "registration_endpoint": { + } + }, + "required": [ + "key", + "value" + ] + }, + "UpdateStudioVariableRequest": { + "type": "object", + "description": "更新 Studio 明文变量请求参数", + "properties": { + "key": { "type": "string", + "description": "明文变量名称", + "maxLength": 128, "examples": [ - "https://modelscope.cn/openapi/v1/agent_ids" + "GRADIO_TEMP_DIR" ] }, - "activity_endpoint": { - "type": "string" + "value": { + "type": "string", + "description": "明文变量新值", + "maxLength": 4096, + "examples": [ + "/new/path" + ] }, - "id_token_signing_alg_values_supported": { + "description": { "type": "string", + "description": "明文变量描述(用途说明),非必填;更新时不传则保留原有描述", + "maxLength": 512, "examples": [ - "EdDSA" + "Gradio 运行时临时目录" ] } - } + }, + "required": [ + "key", + "value" + ] }, - "JWKS": { + "DeleteStudioVariableRequest": { "type": "object", - "description": "JSON Web Key Set", + "description": "删除 Studio 明文变量请求参数", "properties": { - "keys": { - "type": "array", - "items": { - "$ref": "#/components/schemas/JWK" - } + "key": { + "type": "string", + "description": "明文变量名称" } - } + }, + "required": [ + "key" + ] }, - "Collection": { + "StudioVariableItem": { "type": "object", - "description": "Collection 详情对象", + "description": "明文变量列表项(返回 key 和 value)", "properties": { - "slug": { + "key": { "type": "string", - "description": "Collection 唯一标识,格式 {owner}/{slug}。新建为 {owner}/{title_slug};历史为 {owner}/{title_slug}-{short_id}", - "examples": [ - "iSolver/best-nlp-models-6a3b2c" - ] + "description": "明文变量名称" }, - "title": { + "value": { "type": "string", - "description": "Collection 标题", - "examples": [ - "最佳 NLP 模型合集" - ] + "description": "明文变量值" }, "description": { - "type": [ - "string", - "null" - ], - "description": "Collection 描述(Markdown)", - "examples": [ - "精选的自然语言处理模型..." - ] - }, - "owner": { - "type": "string", - "description": "所有者(用户名或组织名)", - "examples": [ - "iSolver" - ] - }, - "visibility": { "type": "string", - "description": "可见性:public(公开)/ private(私有)", - "enum": [ - "public", - "private" - ], - "examples": [ - "public" - ] + "description": "明文变量描述(用途说明),未填写时不返回", + "maxLength": 512 + } + }, + "required": [ + "key", + "value" + ] + }, + "ListStudioVariablesResponse": { + "type": "object", + "description": "获取 Studio 明文变量列表响应", + "properties": { + "success": { + "type": "boolean" }, - "theme": { - "type": [ - "string", - "null" - ], - "description": "主题标签,枚举值:Blue/Pink/Purple/Cyan", + "data": { + "type": "object", + "properties": { + "variables": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StudioVariableItem" + } + } + } + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "SkillLocale": { + "type": "object", + "description": "技能的单语言本地化信息", + "properties": { + "description": { + "type": "string", + "description": "技能描述的本地化翻译" + }, + "category": { + "type": "string", + "description": "技能分类的本地化翻译" + } + } + }, + "SkillLocales": { + "type": "object", + "description": "技能的多语言本地化信息", + "properties": { + "en": { + "$ref": "#/components/schemas/SkillLocale" + }, + "zh": { + "$ref": "#/components/schemas/SkillLocale" + } + } + }, + "SkillSummary": { + "type": "object", + "description": "技能摘要信息", + "properties": { + "_id": { + "type": "string", + "description": "技能内部唯一标识", "examples": [ - "Blue" + "62+FGAr3b7f4Bblezwx8hQ==" ] }, - "item_count": { - "type": "integer", - "description": "Collection 中条目数量", + "id": { + "type": "string", + "description": "技能 ID,格式为 @author/skill_name 或 owner/skill_name", "examples": [ - 12 + "@AMap-Web/amap-lbs-skill" ] }, - "likes": { - "type": "integer", - "description": "喜欢数", + "display_name": { + "type": "string", + "description": "技能展示名称", "examples": [ - 128 + "高德地图综合服务Skill" ] }, - "view_count": { - "type": "integer", - "description": "浏览量", + "description": { + "type": "string", + "description": "技能描述" + }, + "owner": { + "type": "string", + "description": "技能所有者(用户名或组织名)", "examples": [ - 1024 + "guoyanlian01" ] }, - "items": { - "type": "array", - "description": "Collection 中的条目列表", - "items": { - "$ref": "#/components/schemas/CollectionItem" - } - }, - "created_at": { + "license": { "type": "string", - "format": "date-time", - "description": "创建时间,ISO 8601 UTC", + "description": "许可证", "examples": [ - "2025-01-15T08:00:00Z" + "MIT License" ] }, - "updated_at": { + "developer": { "type": "string", - "format": "date-time", - "description": "更新时间,ISO 8601 UTC", + "description": "开发者", "examples": [ - "2025-04-20T10:30:00Z" + "AMap-Web" ] }, - "url": { + "source_url": { "type": "string", - "description": "Collection 页面 URL", + "description": "源码地址" + }, + "category": { + "type": "string", + "description": "技能分类", "examples": [ - "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + "developer-tools" ] }, - "install_command": { + "tags": { "type": [ "array", "null" ], - "description": "当 items 包含 skill 时,返回下载命令数组", + "description": "标签列表,格式为 `key:value`,key 包括 category / license / developer / custom_tag", "items": { "type": "string" }, "examples": [ [ - "modelscope skill install @iSolver/my-skill" + "category:developer-tools", + "license:MIT License", + "developer:AMap-Web", + "custom_tag:api-design" ] ] - } - }, - "required": [ - "slug", - "title", - "owner", - "visibility", - "item_count", - "likes", - "view_count", - "items", - "created_at", - "updated_at", - "url" - ] - }, - "CollectionItem": { - "type": "object", - "description": "Collection 条目对象。条目通过 item_type + item_object_id 唯一标识,不维护独立 item_id", - "properties": { - "item_type": { - "type": "string", - "description": "资源类型:model / dataset / studio / paper / skill / mcp", - "enum": [ - "model", - "dataset", - "studio", - "paper", - "skill", - "mcp" - ], - "examples": [ - "model" - ] }, - "item_object_id": { + "logo_url": { "type": "string", - "description": "资源标识,如 damo/nlp_bert_base 或论文 ID", - "examples": [ - "damo/nlp_bert_base" - ] - }, - "note": { - "type": [ - "string", - "null" - ], - "description": "用户对该条目的备注说明(Markdown)", - "examples": [ - "这个模型在中文 NLP 任务上表现优异" - ] + "description": "logo 图片 URL" }, - "position": { + "view_count": { "type": "integer", - "description": "排序位置(从 1 开始)", + "description": "浏览量", "examples": [ - 1 + 13460 ] }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "添加时间,ISO 8601 UTC", + "downloads": { + "type": "integer", + "description": "下载量", "examples": [ - "2025-01-15T08:00:00Z" + 1203 ] }, - "visibility": { - "type": "string", - "description": "可见性:public / protected / private。protected 对应“仅公开体验”语义", - "enum": [ - "public", - "protected", - "private" - ], - "examples": [ - "public" - ] + "locales": { + "$ref": "#/components/schemas/SkillLocales" }, - "gated": { + "private": { "type": "boolean", - "description": "visibility 为 public 时,是否需先申请并经同意后才可访问", + "description": "是否为私有技能", "examples": [ false ] }, - "protected_mode": { - "type": "integer", - "description": "protected(仅公开体验)模式标识;用于表达条目/资源的仅公开体验语义,具体取值含义以产品后端实现为准(如 0=无、2=仅公开体验)", + "custom_tag": { + "type": [ + "array", + "null" + ], + "description": "自定义标签列表(无标签时为 null)", + "items": { + "type": "string" + }, "examples": [ - 0 - ] - } - }, - "required": [ - "item_type", - "item_object_id", - "position", - "created_at", - "visibility", - "gated", - "protected_mode" - ] - }, - "FailedCollectionItem": { - "type": "object", - "description": "批量操作中失败的条目及原因", - "properties": { - "item_type": { - "type": "string", - "description": "资源类型", - "examples": [ - "model" - ] - }, - "item_object_id": { - "type": "string", - "description": "资源标识", - "examples": [ - "not/exist-model" - ] - }, - "reason": { - "type": "string", - "description": "失败原因错误码,如 ResourceNotFound、ItemAlreadyInCollection、ItemNotInCollection、PermissionDenied", - "examples": [ - "ResourceNotFound" - ] - } - }, - "required": [ - "item_type", - "item_object_id", - "reason" - ] - }, - "CreateCollectionRequest": { - "type": "object", - "description": "创建 Collection 请求", - "properties": { - "title": { - "type": "string", - "maxLength": 128, - "description": "Collection 标题(最长 128 字符)", - "examples": [ - "最佳 NLP 模型合集" - ] - }, - "owner": { - "type": "string", - "description": "所有者(用户名或组织名)", - "examples": [ - "iSolver" - ] - }, - "description": { - "type": [ - "string", - "null" - ], - "description": "Collection 描述(Markdown)", - "examples": [ - "精选的自然语言处理模型..." - ] - }, - "visibility": { - "type": "string", - "description": "可见性:public / private,默认 public", - "enum": [ - "public", - "private" - ], - "default": "public", - "examples": [ - "public" - ] - }, - "theme": { - "type": [ - "string", - "null" - ], - "description": "主题标签,枚举值:Blue/Pink/Purple/Cyan", - "examples": [ - "Blue" - ] - } - }, - "required": [ - "title", - "owner" - ] - }, - "UpdateCollectionRequest": { - "type": "object", - "description": "更新 Collection 请求。传入哪个字段即更新哪个字段,未传字段保持原值", - "properties": { - "title": { - "type": [ - "string", - "null" - ], - "maxLength": 128, - "description": "Collection 标题(最长 128 字符)", - "examples": [ - "最佳 NLP 模型合集(已更名)" - ] - }, - "description": { - "type": [ - "string", - "null" - ], - "description": "Collection 描述(Markdown)", - "examples": [ - "更新后的描述..." - ] - }, - "owner": { - "type": [ - "string", - "null" - ], - "description": "所有者", - "examples": [ - "iSolver" - ] - }, - "visibility": { - "type": [ - "string", - "null" - ], - "description": "可见性:public / private", - "enum": [ - "public", - "private", - null - ], - "examples": [ - "private" - ] - }, - "theme": { - "type": [ - "string", - "null" - ], - "description": "主题标签,枚举值:Blue/Pink/Purple/Cyan", - "examples": [ - "Purple" + [ + "api-design", + "general-tools" + ], + null ] } } }, - "CollectionResponse": { + "SkillDetail": { "type": "object", - "description": "获取 Collection 详情的标准响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", - "examples": [ - true - ] - }, - "request_id": { - "type": "string", - "description": "请求 ID", - "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" - ] + "description": "技能详细信息,包含安装命令", + "allOf": [ + { + "$ref": "#/components/schemas/SkillSummary" }, - "data": { - "$ref": "#/components/schemas/Collection" + { + "type": "object", + "properties": { + "install_command": { + "type": "array", + "description": "安装命令列表", + "items": { + "type": "string" + }, + "examples": [ + [ + "npx skills add https://modelscope.cn/skills/@AMap-Web/amap-lbs-skill", + "curl -fsSL https://modelscope.cn/skills/install.sh | bash -s -- @AMap-Web/amap-lbs-skill" + ] + ] + } + } } - }, - "required": [ - "success", - "request_id", - "data" ] }, - "CollectionListResponse": { + "SkillListResponse": { "type": "object", - "description": "获取 Collection 列表的标准响应", + "description": "技能列表响应", "properties": { "success": { "type": "boolean", - "description": "成功时始终为 true", "examples": [ true ] }, "request_id": { "type": "string", - "description": "请求 ID", "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "c5bca202-aa4f-478b-b7e2-c568160583cb" ] }, "data": { "type": "object", - "description": "列表数据", "properties": { - "collection_list": { + "skills": { "type": "array", - "description": "Collection 列表,返回完整 Collection 对象", + "description": "技能列表", "items": { - "$ref": "#/components/schemas/Collection" + "$ref": "#/components/schemas/SkillSummary" } }, - "total_count": { + "total": { "type": "integer", - "description": "符合筛选条件的合集总数", + "format": "int64", + "description": "总记录数", "examples": [ - 128 + 20 ] }, "page_number": { @@ -6985,851 +7464,1068 @@ }, "page_size": { "type": "integer", - "description": "每页大小", + "description": "每页条数", "examples": [ - 10 + 20 ] } - }, - "required": [ - "collection_list", - "total_count", - "page_number", - "page_size" - ] + } } - }, - "required": [ - "success", - "request_id", - "data" - ] + } }, - "CollectionItemListResponse": { + "SkillDetailResponse": { "type": "object", - "description": "获取 Collection 条目列表的标准响应", + "description": "技能详情响应", "properties": { "success": { "type": "boolean", - "description": "成功时始终为 true", "examples": [ true ] }, "request_id": { "type": "string", - "description": "请求 ID", "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "105c898f-fd6d-437d-aa0b-9006036782da" ] }, "data": { - "type": "object", - "description": "条目列表数据", - "properties": { - "items": { - "type": "array", - "description": "条目列表", - "items": { - "$ref": "#/components/schemas/CollectionItem" - } - }, - "total_count": { - "type": "integer", - "description": "条目总数", - "examples": [ - 50 - ] - }, - "page_number": { - "type": "integer", - "description": "当前页码", - "examples": [ - 1 - ] - }, - "page_size": { - "type": "integer", - "description": "每页大小", - "examples": [ - 10 - ] - } - }, - "required": [ - "items", - "total_count", - "page_number", - "page_size" - ] + "$ref": "#/components/schemas/SkillDetail" } - }, - "required": [ - "success", - "request_id", - "data" - ] + } }, - "CollectionItemRequest": { + "CreateSkillRequest": { "type": "object", - "description": "待添加条目", + "description": "创建 Skill 请求参数", "properties": { - "item_type": { + "skill_name": { "type": "string", - "description": "资源类型:model / dataset / studio / paper / skill / mcp", - "enum": [ - "model", - "dataset", - "studio", - "paper", - "skill", - "mcp" - ], + "description": "英文名称,创建后不可更改,仅允许小写字母、数字和连字符", + "pattern": "^[a-z0-9-]+$", + "maxLength": 64, "examples": [ - "model" + "my-awesome-skill" ] }, - "item_object_id": { + "owner": { "type": "string", - "description": "资源标识,如 damo/nlp_bert_base_chinese", + "description": "所有者(用户名或组织名)", "examples": [ - "damo/nlp_bert_base_chinese" + "iSolver" ] }, - "note": { - "type": [ - "string", - "null" - ], - "description": "备注(Markdown)", + "display_name": { + "type": "string", + "description": "展示名称", + "maxLength": 128, "examples": [ - "推荐用于中文文本分类" + "我的技能" ] }, - "position": { - "type": [ - "integer", - "null" + "source_url": { + "type": "string", + "format": "uri", + "description": "来源地址", + "examples": [ + "https://github.com/example/my-skill" + ] + }, + "private": { + "type": "boolean", + "description": "是否私有,默认 false(公开)", + "default": false, + "examples": [ + false + ] + }, + "description": { + "type": "string", + "description": "描述", + "maxLength": 2000, + "examples": [ + "这是一个示例技能" + ] + }, + "license": { + "type": "string", + "description": "开源协议证书,不填默认 Apache-2.0", + "default": "Apache-2.0", + "examples": [ + "Apache-2.0" + ] + }, + "category": { + "allOf": [ + { + "$ref": "#/components/schemas/SkillCategory" + } ], - "description": "排序位置(从 1 开始)", + "description": "Skill 类型/分类", "examples": [ - 1 + "developer-tools" ] - } - }, - "required": [ - "item_type", - "item_object_id" - ] - }, - "AddCollectionItemsRequest": { - "type": "object", - "description": "批量添加条目请求", - "properties": { - "items": { + }, + "tags": { "type": "array", - "description": "待添加条目列表(最少 1 个元素)", - "minItems": 1, + "description": "自定义标签", "items": { - "$ref": "#/components/schemas/CollectionItemRequest" - } + "type": "string" + }, + "examples": [ + [ + "Skill Creation" + ] + ] + }, + "logo_url": { + "type": "string", + "format": "uri", + "description": "Skill 图标 URL", + "examples": [ + "https://example.com/logo.png" + ] + }, + "skill_file": { + "type": "string", + "description": "Skill 项目文件 ID(通过 POST /files/upload 上传 zip 包后获取)。\nzip 包要求:\n- 最大 5MB\n- zip 根目录下必须包含且仅包含 1 个 SKILL.md 文件\n- 可包含子目录及其他辅助文件(如 prompts/、examples/ 等)\n- SKILL.md 必须包含 YAML front-matter(name、version、description)\n", + "examples": [ + "8c378570-8991-431b-a82c-96f3d0b4f0f4" + ] } }, "required": [ - "items" + "skill_name", + "owner", + "license", + "category", + "skill_file" ] }, - "AddCollectionItemsResponse": { + "CreateSkillResponse": { "type": "object", - "description": "批量添加条目的标准响应", + "description": "创建 Skill 响应", "properties": { "success": { "type": "boolean", - "description": "成功时始终为 true(部分失败仍为 true,失败项见 failed_items)", "examples": [ true ] }, "request_id": { "type": "string", - "description": "请求 ID", "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "c55709d2-4e09-4bd8-be84-d8518bc46363" ] }, "data": { "type": "object", - "description": "添加结果", "properties": { - "added_count": { - "type": "integer", - "description": "成功添加的条目数量", + "id": { + "type": "string", + "description": "Skill ID (owner/skill_name)", "examples": [ - 2 + "iSolver/my-awesome-skill" ] }, - "url": { + "name": { "type": "string", - "description": "Collection 页面 URL", + "description": "Skill 英文名称", "examples": [ - "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + "my-awesome-skill" ] }, - "failed_items": { - "type": "array", - "description": "添加失败的条目及原因", - "items": { - "$ref": "#/components/schemas/FailedCollectionItem" - } + "display_name": { + "type": "string", + "description": "展示名称", + "examples": [ + "我的技能" + ] + }, + "owner": { + "type": "string", + "description": "所有者", + "examples": [ + "iSolver" + ] + }, + "url": { + "type": "string", + "description": "Skill 页面 URL", + "examples": [ + "https://modelscope.cn/skills/iSolver/my-awesome-skill" + ] } - }, - "required": [ - "added_count", - "url", - "failed_items" - ] + } } }, "required": [ "success", - "request_id", - "data" + "request_id" ] }, - "UpdateCollectionItemRequest": { + "UpdateSkillSettingsRequest": { "type": "object", - "description": "更新单个条目请求。传入哪个字段即更新哪个字段", + "description": "更新 Skill 设置请求参数(传入哪个字段修改哪个字段,不传的字段不修改)", "properties": { - "note": { - "type": [ - "string", - "null" - ], - "description": "备注(Markdown)", + "display_name": { + "type": "string", + "description": "展示名称", + "maxLength": 128, "examples": [ - "更新后的备注" + "新的展示名称" ] }, - "position": { - "type": [ - "integer", - "null" - ], - "description": "排序位置(从 1 开始)", + "source_url": { + "type": "string", + "format": "uri", + "description": "来源地址", "examples": [ - 2 + "https://github.com/example/my-skill" ] - } - } - }, - "UpdateCollectionItemResponse": { - "type": "object", - "description": "更新单个条目的标准响应", - "properties": { - "success": { + }, + "private": { "type": "boolean", - "description": "成功时始终为 true", + "description": "是否私有", "examples": [ true ] }, - "request_id": { + "description": { "type": "string", - "description": "请求 ID", + "description": "描述", + "maxLength": 2000, "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "更新后的技能描述" ] }, - "data": { - "$ref": "#/components/schemas/CollectionItem" - } - }, - "required": [ - "success", - "request_id", - "data" - ] - }, - "CreateGalleryRequest": { - "type": "object", - "description": "创建 Gallery 请求", - "properties": { - "name": { + "license": { "type": "string", - "maxLength": 128, - "pattern": "^[0-9a-zA-Z_.-]+$", - "description": "Gallery 名称,最长 128 字符,仅允许字母、数字、下划线、连字符、点", + "description": "开源协议证书", "examples": [ - "my-gallery" + "Apache-2.0" ] }, - "owner": { - "type": "string", - "maxLength": 128, - "description": "所属用户或组织名,最长 128 字符", + "category": { + "allOf": [ + { + "$ref": "#/components/schemas/SkillCategory" + } + ], + "description": "Skill 类型/分类", "examples": [ - "my-org" + "developer-tools" ] }, - "files": { + "tags": { "type": "array", - "maxItems": 50, - "description": "文件 ID 列表(文件上传接口返回的 ID),最多 50 个,单个元素不能为空", + "description": "自定义标签(传入时整体覆盖原标签列表)", "items": { - "type": "string", - "minLength": 1 + "type": "string" }, "examples": [ [ - "8c378570-8991-431b-a82c-96f3d0b4f0f4" + "Image/Video Gen", + "Media Processing" ] ] }, - "entry_file": { + "logo_url": { "type": "string", - "minLength": 1, - "description": "入口文件的相对路径,对应 files 中某个已上传文件;后缀须与 category 匹配(后缀不区分大小写):notebook→.ipynb,website→.html,pdf→.pdf(file 类别不限后缀)", - "examples": [ - "notebook/demo.ipynb" - ] - }, - "private": { - "type": "boolean", - "default": false, - "description": "是否私有,默认 false(公开)", + "format": "uri", + "description": "Skill 图标 URL(传空字符串恢复为平台默认图标)", "examples": [ - false + "https://example.com/new-logo.png" ] }, - "category": { + "skill_file": { "type": "string", - "description": "类型(必传),可选值:notebook / website / pdf / file;除 file 外须与 entry_file 后缀匹配(后缀不区分大小写):notebook→.ipynb,website→.html,pdf→.pdf", - "enum": [ - "notebook", - "website", - "pdf", - "file" - ], + "description": "Skill 项目文件 ID(通过 POST /files/upload 上传 zip 包后获取,传入时整体覆盖原文件,不传则保留原文件不变)。\nzip 包要求:\n- 最大 5MB\n- zip 根目录下必须包含且仅包含 1 个 SKILL.md 文件\n- 可包含子目录及其他辅助文件(如 prompts/、examples/ 等)\n- SKILL.md 必须包含 YAML front-matter(name、version、description)\n", "examples": [ - "notebook" + "new-file-id-xxx" ] - }, - "labels": { - "type": [ - "array", - "null" - ], - "maxItems": 10, - "description": "标签列表,最多 10 个,单个标签最长 20 字符", - "items": { - "type": "string", - "maxLength": 20 - }, + } + } + }, + "SkillCategory": { + "type": "string", + "description": "Skill 类型/分类枚举", + "enum": [ + "skill-management", + "developer-tools", + "marketing-seo", + "frontend-development", + "ai-media", + "code-quality-testing", + "mobile-development", + "cloud-devops", + "other" + ], + "examples": [ + "developer-tools" + ] + }, + "AgentSystemFileConfig": { + "type": "object", + "description": "Agent 系统文件配置", + "properties": { + "name": { + "type": "string", + "description": "文件名", "examples": [ - [ - "demo", - "tutorial" - ] + "agent.json" ] }, - "path": { - "type": [ - "string", - "null" - ], - "maxLength": 128, - "pattern": "^[0-9a-zA-Z_-]+$", - "description": "自定义 URL 尾缀(选填),仅允许字母、数字、下划线、连字符,最长 128 字符;不填则不生成,填写后灵感流对应 URL:https://modelscope.cn/gallery/{owner}/{path}", + "content": { + "type": "string", + "description": "文件内容", "examples": [ - "my-custom-path" + "{...}" ] } - }, - "required": [ - "name", - "owner", - "files", - "entry_file", - "category" - ] + } }, - "CreateGalleryResponse": { + "AgentModelsConfig": { "type": "object", - "description": "创建 Gallery 的标准响应", + "description": "Agent 关联模型配置", "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true", + "model_name": { + "type": "string", + "description": "模型名称", "examples": [ - true + "qwen-max" ] }, - "request_id": { + "path": { "type": "string", - "description": "请求 ID", + "description": "模型路径" + }, + "provider_id": { + "type": "string", + "description": "模型提供商 ID", "examples": [ - "req-xxxxxx" + "dashscope" ] }, - "data": { - "type": "object", - "description": "创建结果", - "properties": { - "id": { - "type": "string", - "description": "Gallery 唯一标识(UUID)", - "examples": [ - "a1b2c3d4-e5f6-7890-abcd-ef1234567890" - ] - }, - "url": { - "type": "string", - "description": "Gallery 访问链接", - "examples": [ - "https://modelscope.cn/gallery/my-org/a1b2c3d4-e5f6-7890-abcd-ef1234567890" - ] - } - }, - "required": [ - "id", - "url" + "base_url": { + "type": "string", + "description": "模型访问 Base URL", + "examples": [ + "https://dashscope.aliyuncs.com" + ] + }, + "protocol": { + "type": "string", + "description": "通信协议", + "examples": [ + "openai" + ] + }, + "provider": { + "type": "string", + "description": "模型提供商名称", + "examples": [ + "DashScope" ] } - }, - "required": [ - "success", - "request_id", - "data" - ] + } }, - "BatchUpdateCollectionItem": { + "AgentMcpToolsConfig": { "type": "object", - "description": "批量更新条目。通过 item_type + item_object_id 定位,传入哪个字段就修改哪个字段", + "description": "MCP 工具配置", "properties": { - "item_type": { + "name": { "type": "string", - "description": "资源类型,用于定位", - "enum": [ - "model", - "dataset", - "studio", - "paper", - "skill", - "mcp" - ], + "description": "工具名称", "examples": [ - "model" + "get_weather" ] }, - "item_object_id": { + "description": { "type": "string", - "description": "资源标识,用于定位", + "description": "工具描述", "examples": [ - "damo/nlp_bert_base_chinese" + "获取指定城市天气" + ] + } + } + }, + "AgentMcpConfig": { + "type": "object", + "description": "Agent 关联 MCP 配置", + "properties": { + "name": { + "type": "string", + "description": "MCP 服务名称", + "examples": [ + "weather-mcp" ] }, - "note": { - "type": [ - "string", - "null" - ], - "description": "备注(Markdown),不传则不修改", + "description": { + "type": "string", + "description": "MCP 服务描述", "examples": [ - "更新后的备注" + "天气查询 MCP" ] }, - "position": { + "tools": { "type": [ - "integer", + "array", "null" ], - "description": "排序位置(从 1 开始),不传则不修改", - "examples": [ - 1 - ] + "description": "工具列表,可能为 null", + "items": { + "$ref": "#/components/schemas/AgentMcpToolsConfig" + } } - }, - "required": [ - "item_type", - "item_object_id" - ] + } }, - "UpdateCollectionItemsRequest": { + "AgentSkillConfig": { "type": "object", - "description": "批量更新条目请求", + "description": "Agent 关联技能配置", "properties": { - "items": { - "type": "array", - "description": "待更新条目列表(最少 1 个元素)", - "minItems": 1, - "items": { - "$ref": "#/components/schemas/BatchUpdateCollectionItem" - } - } - }, - "required": [ - "items" - ] - }, - "UpdateCollectionItemsResponse": { - "type": "object", - "description": "批量更新条目的标准响应", - "properties": { - "success": { - "type": "boolean", - "description": "成功时始终为 true(部分失败仍为 true,失败项见 failed_items)", + "name": { + "type": "string", + "description": "技能名称", "examples": [ - true + "search-skill" ] }, - "request_id": { + "description": { "type": "string", - "description": "请求 ID", + "description": "技能描述", "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "搜索技能" ] }, - "data": { - "type": "object", - "description": "批量更新结果", - "properties": { - "updated_count": { - "type": "integer", - "description": "成功更新的条目数量", - "examples": [ - 2 - ] - }, - "failed_items": { - "type": "array", - "description": "更新失败的条目及原因", - "items": { - "$ref": "#/components/schemas/FailedCollectionItem" - } - } - }, - "required": [ - "updated_count", - "failed_items" + "content": { + "type": "string", + "description": "技能内容(README)", + "examples": [ + "# Search Skill\n..." ] } - }, - "required": [ - "success", - "request_id", - "data" - ] + } }, - "UserPublic": { + "CreateAgentRequest": { "type": "object", - "description": "用户公开信息", + "description": "创建 Agent 请求参数", "properties": { - "username": { + "owner": { "type": "string", - "description": "用户名", + "description": "所有者(用户名或组织名)", "examples": [ - "tester" + "my_org" ] }, - "nickname": { + "agent_name": { "type": "string", - "description": "昵称", + "description": "Agent 唯一名称", "examples": [ - "测试用户" + "weather-agent" + ] + }, + "display_name": { + "type": "string", + "description": "显示名称", + "examples": [ + "天气助手" ] }, "description": { "type": "string", - "description": "描述", + "description": "描述信息", "examples": [ - "这是一个测试用户" + "一个可以查询天气的 Agent" ] }, - "avatar_url": { + "logo_url": { "type": "string", - "format": "uri", - "description": "头像图片URL地址", + "description": "Logo URL", "examples": [ - "https://resouces.modelscope.cn/avatar/516aa51f-5fb7-49eb-930c-e90500ae13ca.png" + "https://example.com/logo.png" ] - } - }, - "required": [ - "username" - ] - }, - "UserPrivate": { - "type": "object", - "description": "用户私有信息(仅自己可见)", - "properties": { - "email": { + }, + "framework": { "type": "string", - "format": "email", - "description": "邮箱地址", + "description": "Agent 框架类型", "examples": [ - "test@modelscope.cn" + "ms-agent", + "qwenpaw" ] - } - } - }, - "McpServerList": { - "type": "object", - "description": "MCP服务列表与分页信息", - "properties": { - "mcp_server_list": { + }, + "license": { + "type": "string", + "description": "许可证类型", + "examples": [ + "Apache License 2.0" + ] + }, + "private": { + "type": "boolean", + "description": "是否为私有 Agent,默认 false(公开)", + "default": false, + "examples": [ + false + ] + }, + "custom_tags": { "type": "array", - "description": "MCP服务列表", + "description": "自定义标签列表", "items": { - "$ref": "#/components/schemas/McpServerSummary" - } - }, - "total_count": { - "type": "integer", - "description": "搜索符合条件的mcp server总个数", + "type": "string" + }, "examples": [ - 100 + [ + "weather", + "demo" + ] ] } }, "required": [ - "mcp_server_list", - "total_count" + "owner", + "agent_name", + "framework" ] }, - "McpServerOperationalList": { + "CreateAgentResponse": { "type": "object", - "description": "用户托管MCP服务列表", + "description": "创建 Agent 响应", "properties": { - "mcp_server_list": { - "type": "array", - "description": "用户托管MCP服务列表", - "items": { - "$ref": "#/components/schemas/McpServerOperational" - } + "success": { + "type": "boolean", + "examples": [ + true + ] }, - "total_count": { - "type": "integer", - "description": "服务链接总数", + "request_id": { + "type": "string", "examples": [ - 5 + "2f1c8a3e-9b7d-4f21-a0c6-1e2d3f4a5b6c" ] + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Agent 全局 ID,格式为 owner/agent_name", + "examples": [ + "my_org/weather-agent" + ] + }, + "name": { + "type": "string", + "description": "Agent 名称", + "examples": [ + "weather-agent" + ] + }, + "display_name": { + "type": "string", + "description": "显示名称", + "examples": [ + "天气助手" + ] + }, + "owner": { + "type": "string", + "description": "所有者", + "examples": [ + "my_org" + ] + }, + "url": { + "type": "string", + "description": "Agent 访问 URL", + "examples": [ + "https://modelscope.cn/agents/my_org/weather-agent" + ] + } + } } }, "required": [ - "mcp_server_list", - "total_count" + "success", + "request_id" ] }, - "data": { + "AgentDetail": { "type": "object", + "description": "Agent 详细信息", "properties": { "id": { "type": "string", - "description": "Studio ID (owner/repo_name)" - }, - "repo_name": { - "type": "string", - "description": "仓库名称" + "description": "Agent ID,格式为 path/name", + "examples": [ + "my_org/weather-agent" + ] }, "display_name": { "type": "string", - "description": "显示名称" + "description": "显示名称", + "examples": [ + "天气助手" + ] }, - "owner": { + "description": { "type": "string", - "description": "所有者" + "description": "描述信息", + "examples": [ + "一个可以查询天气的 Agent" + ] }, - "description": { + "framework": { "type": "string", - "description": "描述" + "description": "框架类型", + "examples": [ + "ms-agent" + ] }, - "cover_image": { + "logo_url": { "type": "string", - "description": "封面图 URL" + "description": "Logo URL", + "examples": [ + "https://example.com/logo.png" + ] }, - "likes": { - "type": "integer", - "description": "喜欢数" + "license": { + "type": "string", + "description": "许可证类型", + "examples": [ + "Apache License 2.0" + ] }, - "view_count": { - "type": "integer", - "description": "访问量" + "private": { + "type": "boolean", + "description": "是否为私有 Agent", + "examples": [ + false + ] }, - "tags": { - "type": "array", + "custom_tags": { + "type": [ + "array", + "null" + ], + "description": "自定义标签列表(无标签时为 null)", "items": { "type": "string" - } - }, - "visibility": { - "$ref": "#/components/schemas/StudioVisibility" - }, - "private": { - "type": "boolean", - "description": "Deprecated: 请使用 visibility 字段", - "deprecated": true + }, + "examples": [ + [ + "weather", + "demo" + ] + ] }, - "last_modified": { - "type": "string", - "description": "最后修改时间" + "categories": { + "type": [ + "array", + "null" + ], + "description": "分类标签列表(无分类时为 null)", + "items": { + "type": "string" + }, + "examples": [ + [ + "tools" + ] + ] }, - "sdk_type": { - "$ref": "#/components/schemas/StudioSDKType" + "last_modified": { + "type": "string", + "format": "date-time", + "description": "最后修改时间(UTC,RFC3339)", + "examples": [ + "2026-07-16T10:30:00Z" + ] }, - "sdk_version": { - "$ref": "#/components/schemas/StudioSDKVersion" + "downloads": { + "type": "integer", + "format": "int64", + "description": "下载量", + "examples": [ + 15 + ] }, - "hardware": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioHardware" - } - ], - "description": "期望的硬件配置(数据库中保存的值)" + "likes": { + "type": "integer", + "format": "int64", + "description": "喜欢数", + "examples": [ + 8 + ] }, - "base_image": { - "allOf": [ - { - "$ref": "#/components/schemas/StudioBaseImage" - } - ], - "description": "期望的基础镜像(Docker 类型不返回)" + "mcps": { + "type": "array", + "description": "关联 MCP 配置列表", + "items": { + "$ref": "#/components/schemas/AgentMcpConfig" + } }, - "license": { - "type": "string", - "description": "许可证" + "skills": { + "type": "array", + "description": "关联技能配置列表", + "items": { + "$ref": "#/components/schemas/AgentSkillConfig" + } }, - "host": { - "type": "string", - "description": "访问地址" + "system_files": { + "type": "array", + "description": "系统文件列表", + "items": { + "$ref": "#/components/schemas/AgentSystemFileConfig" + } }, - "mcp_support": { + "models": { + "type": "array", + "description": "关联模型配置列表", + "items": { + "$ref": "#/components/schemas/AgentModelsConfig" + } + }, + "readme": { + "type": "string", + "description": "README 内容", + "examples": [ + "# Weather Agent\n使用说明..." + ] + } + } + }, + "GetAgentResponse": { + "type": "object", + "description": "Agent 详情响应", + "properties": { + "success": { "type": "boolean", - "description": "是否支持 MCP" + "examples": [ + true + ] }, - "runtime": { - "$ref": "#/components/schemas/StudioRuntime" + "request_id": { + "type": "string", + "examples": [ + "7a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" + ] + }, + "data": { + "$ref": "#/components/schemas/AgentDetail" } } }, - "ListStudiosResponse": { + "AgentSummary": { "type": "object", - "description": "获取创空间列表响应", + "description": "Agent 摘要信息", "properties": { - "studios": { + "id": { + "type": "string", + "description": "Agent ID,格式为 path/name", + "examples": [ + "my_org/weather-agent" + ] + }, + "name": { + "type": "string", + "description": "Agent 名称", + "examples": [ + "weather-agent" + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "my_org" + ] + }, + "display_name": { + "type": "string", + "description": "展示名称", + "examples": [ + "天气助手" + ] + }, + "description": { + "type": "string", + "description": "描述信息" + }, + "framework": { + "type": "string", + "description": "框架类型", + "examples": [ + "ms-agent" + ] + }, + "logo_url": { + "type": "string", + "description": "Logo URL" + }, + "license": { + "type": "string", + "description": "许可证类型", + "examples": [ + "Apache License 2.0" + ] + }, + "private": { + "type": "boolean", + "description": "是否为私有 Agent", + "examples": [ + false + ] + }, + "categories": { + "type": [ + "array", + "null" + ], + "description": "分类标签列表(无分类时为 null)", + "items": { + "type": "string" + }, + "examples": [ + [ + "tools" + ] + ] + }, + "custom_tags": { + "type": [ + "array", + "null" + ], + "description": "自定义标签列表(无标签时为 null)", + "items": { + "type": "string" + }, + "examples": [ + [ + "weather", + "demo" + ] + ] + }, + "tags": { "type": "array", - "description": "创空间列表", + "description": "标签列表,格式为 `key:value`,key 包括 category / license / framework / custom_tag", "items": { - "$ref": "#/components/schemas/data" - } + "type": "string" + }, + "examples": [ + [ + "category:tools", + "license:Apache License 2.0", + "framework:ms-agent", + "custom_tag:weather" + ] + ] }, - "total_count": { + "views": { "type": "integer", - "description": "符合筛选条件的创空间总数", + "format": "int64", + "description": "浏览量", "examples": [ - 120 + 319 ] }, - "page_number": { + "downloads": { "type": "integer", - "description": "当前页码", + "format": "int64", + "description": "下载量", "examples": [ - 1 + 15 ] }, - "page_size": { + "likes": { "type": "integer", - "description": "每页大小", + "format": "int64", + "description": "喜欢数", "examples": [ - 10 + 8 + ] + }, + "last_modified": { + "type": "string", + "format": "date-time", + "description": "最后修改时间(UTC,RFC3339)", + "examples": [ + "2026-07-16T10:30:00Z" + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "创建时间(UTC,RFC3339)", + "examples": [ + "2026-07-01T08:00:00Z" ] } - }, - "required": [ - "studios", - "total_count", - "page_number", - "page_size" - ] + } }, - "CollectionMutationResponse": { + "AgentListResponse": { "type": "object", - "description": "创建/更新 Collection 的标准响应", + "description": "Agent 列表响应", "properties": { "success": { "type": "boolean", - "description": "成功时始终为 true", "examples": [ true ] }, "request_id": { "type": "string", - "description": "请求 ID", "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "c5bca202-aa4f-478b-b7e2-c568160583cb" ] }, "data": { "type": "object", - "description": "变更结果", "properties": { - "slug": { - "type": "string", - "description": "Collection slug(重命名时可能变化)", + "agents": { + "type": [ + "array", + "null" + ], + "description": "Agent 列表(无结果时为 null)", + "items": { + "$ref": "#/components/schemas/AgentSummary" + } + }, + "total": { + "type": "integer", + "format": "int64", + "description": "总记录数", "examples": [ - "iSolver/best-nlp-models-6a3b2c" + 20 ] }, - "title": { - "type": "string", - "description": "Collection 标题", + "page_number": { + "type": "integer", + "description": "当前页码", "examples": [ - "最佳 NLP 模型合集" + 1 ] }, - "url": { - "type": "string", - "description": "Collection 页面 URL", + "page_size": { + "type": "integer", + "description": "每页条数", "examples": [ - "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + 10 ] } - }, - "required": [ - "slug", - "title", - "url" - ] + } } }, "required": [ "success", - "request_id", - "data" + "request_id" ] }, - "EmptyDataResponse": { + "GetBalanceResponse": { "type": "object", - "description": "删除/移除类操作的标准响应,结果以 success 字段为准,data 为空对象", + "description": "余额查询响应", "properties": { "success": { "type": "boolean", - "description": "成功时始终为 true", "examples": [ true ] }, + "data": { + "type": "object", + "properties": { + "total_balance": { + "type": "number", + "description": "总额度(可用额度 + 预扣额度)", + "examples": [ + 1500 + ] + }, + "available_balance": { + "type": "number", + "description": "可用额度", + "examples": [ + 1300 + ] + }, + "frozen_amount": { + "type": "number", + "description": "预扣额度(进行中任务未返回结果时预扣减)", + "examples": [ + 200 + ] + } + } + }, "request_id": { "type": "string", - "description": "请求 ID", "examples": [ - "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + "c55709d2-4e09-4bd8-be84-d8518bc46363" + ] + } + }, + "required": [ + "success", + "request_id" + ] + }, + "UploadFileRequest": { + "type": "object", + "description": "上传文件请求参数(multipart/form-data)", + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "上传的文件,最大 5MB" + }, + "filename": { + "type": [ + "string", + "null" + ], + "description": "自定义文件名(选填),支持多级目录路径(如 images/sub/logo.png);不提供则使用上传文件的原始文件名。不允许绝对路径或目录穿越(..)。", + "examples": [ + "images/sub/logo.png" + ] + } + }, + "required": [ + "file" + ] + }, + "UploadFileResponse": { + "type": "object", + "description": "上传文件响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "examples": [ + "99ee2083-63e0-46dc-b393-623c22645078" ] }, "data": { "type": "object", - "description": "空对象" + "properties": { + "id": { + "type": "string", + "description": "文件 ID,可在其他接口中作为入参引用该文件", + "examples": [ + "8c378570-8991-431b-a82c-96f3d0b4f0f4" + ] + } + }, + "required": [ + "id" + ] } }, "required": [ @@ -7837,2214 +8533,8132 @@ "request_id", "data" ] - } - }, - "responses": { - "bad-request": { - "description": "请求参数错误", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "input-parameter-error": { - "$ref": "#/components/examples/input-parameter-error" - } - } - } - } }, - "unauthorized": { - "description": "认证失败(token 无效、过期或未提供)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "invalid-authentication": { - "$ref": "#/components/examples/invalid-authentication" - } - } + "JWK": { + "type": "object", + "description": "JSON Web Key(仅用于 Ed25519 公钥)", + "required": [ + "kty", + "crv", + "x", + "kid" + ], + "properties": { + "kty": { + "type": "string", + "description": "密钥类型,固定 OKP", + "enum": [ + "OKP" + ], + "examples": [ + "OKP" + ] + }, + "crv": { + "type": "string", + "description": "椭圆曲线类型,固定 Ed25519", + "enum": [ + "Ed25519" + ], + "examples": [ + "Ed25519" + ] + }, + "x": { + "type": "string", + "description": "公钥 base64url 编码(无 padding)", + "examples": [ + "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs" + ] + }, + "kid": { + "type": "string", + "description": "密钥 ID", + "examples": [ + "agent-key-001" + ] + }, + "alg": { + "type": "string", + "description": "签名算法", + "examples": [ + "EdDSA" + ] + }, + "use": { + "type": "string", + "description": "用途", + "examples": [ + "sig" + ] } } }, - "forbidden": { - "description": "权限不足(token 有效但无权执行该操作,或配额超限)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "operation-not-allowed": { - "$ref": "#/components/examples/operation-not-allowed" - }, - "quota-limit-exceed": { - "$ref": "#/components/examples/quota-limit-exceed" - } - } + "Principal": { + "type": "object", + "description": "Agent 归属主体", + "properties": { + "type": { + "type": "string", + "description": "主体类型", + "enum": [ + "user", + "org" + ], + "examples": [ + "user" + ] + }, + "id": { + "type": "string", + "description": "主体 ID", + "examples": [ + "alice" + ] } } }, - "not-found": { - "description": "资源未找到", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "resource-not-found": { - "$ref": "#/components/examples/resource-not-found" - } - } - } - } + "AgentIdentityStatus": { + "type": "string", + "description": "Agent 状态", + "enum": [ + "active", + "paused" + ], + "examples": [ + "active" + ] }, - "internal-server-error": { - "description": "内部服务错误", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "internal-server-error": { - "$ref": "#/components/examples/internal-server-error" - } - } - } - } - }, - "service-unavailable": { - "description": "服务不可用", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "service-unavailable": { - "$ref": "#/components/examples/service-unavailable" - } - } + "CreateAgentIdentityRequest": { + "type": "object", + "description": "创建 Agent 身份请求", + "required": [ + "agent_name", + "public_key" + ], + "properties": { + "agent_name": { + "type": "string", + "description": "Agent 名称(最长 128 字符)", + "maxLength": 128, + "examples": [ + "my-agent" + ] + }, + "description": { + "type": "string", + "description": "Agent 描述(最长 2000 字符)", + "maxLength": 2000 + }, + "public_key": { + "$ref": "#/components/schemas/JWK" + }, + "key_alg_type": { + "type": "string", + "description": "密钥算法类型,固定 Ed25519", + "enum": [ + "Ed25519" + ], + "examples": [ + "Ed25519" + ] + }, + "token_expire_time": { + "type": "integer", + "description": "Token 过期时间(秒),可选值 300/600/1800/3600", + "enum": [ + 300, + 600, + 1800, + 3600 + ], + "examples": [ + 3600 + ] } } }, - "get-current-user-success": { - "description": "成功获取当前用户信息", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserResponse" - }, - "examples": { - "user-all-response": { - "$ref": "#/components/examples/user-all-response" - } - } + "UpdateAgentIdentityRequest": { + "type": "object", + "description": "更新 Agent 身份设置请求", + "properties": { + "agent_name": { + "type": "string", + "maxLength": 128 + }, + "description": { + "type": "string", + "maxLength": 2000 + }, + "token_expire_time": { + "type": "integer", + "enum": [ + 300, + 600, + 1800, + 3600 + ] } } }, - "list-models-success": { - "description": "成功获取模型列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModelListResponse" - }, - "examples": { - "list-models-response": { - "$ref": "#/components/examples/list-models-response" - } - } + "ResetKeyPairRequest": { + "type": "object", + "description": "重置密钥对请求(客户端本地生成新密钥后上传公钥)", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "$ref": "#/components/schemas/JWK" + }, + "key_alg_type": { + "type": "string", + "enum": [ + "Ed25519" + ], + "examples": [ + "Ed25519" + ] } } }, - "get-model-success": { - "description": "成功获取模型详情", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModelDetailResponse" - }, - "examples": { - "get-model-response": { - "$ref": "#/components/examples/get-model-response" - } - } + "PauseAgentRequest": { + "type": "object", + "description": "启用/停用 Agent 请求", + "required": [ + "paused" + ], + "properties": { + "paused": { + "type": "boolean", + "description": "true 表示停用,false 表示启用", + "examples": [ + true + ] } } }, - "list-datasets-success": { - "description": "成功获取数据集列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DatasetListResponse" - }, - "examples": { - "list-datasets-response": { - "$ref": "#/components/examples/list-datasets-response" - } - } + "AgentIdentity": { + "type": "object", + "description": "Agent 身份详情", + "properties": { + "agent_id": { + "type": "string", + "description": "Agent 唯一 ID", + "examples": [ + "agent_id:modelscope:agent_1234567890ab" + ] + }, + "agent_name": { + "type": "string", + "examples": [ + "my-agent" + ] + }, + "description": { + "type": "string" + }, + "token_expire_time": { + "type": "integer", + "examples": [ + 3600 + ] + }, + "principal": { + "$ref": "#/components/schemas/Principal" + }, + "kid": { + "type": "string", + "description": "当前密钥 ID", + "examples": [ + "agent-key-001" + ] + }, + "public_key": { + "$ref": "#/components/schemas/JWK" + }, + "status": { + "$ref": "#/components/schemas/AgentIdentityStatus" + }, + "create_time": { + "type": "string", + "description": "创建时间 (ISO 8601)", + "examples": [ + "2026-06-03T10:00:00Z" + ] + }, + "update_time": { + "type": "string", + "description": "更新时间 (ISO 8601)", + "examples": [ + "2026-06-03T10:30:00Z" + ] } } }, - "get-dataset-success": { - "description": "成功获取数据集详情", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DatasetDetailResponse" - }, - "examples": { - "get-dataset-response": { - "$ref": "#/components/examples/get-dataset-response" - } - } + "AgentIdentityListItem": { + "type": "object", + "description": "Agent 身份列表项", + "properties": { + "agent_id": { + "type": "string" + }, + "agent_name": { + "type": "string" + }, + "kid": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/AgentIdentityStatus" + }, + "token_expire_time": { + "type": "integer" + }, + "create_time": { + "type": "string" } } }, - "list-mcp-servers-success": { - "description": "成功获取MCP服务列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/McpServerListResponse" - }, - "examples": { - "list-mcp-servers-response": { - "$ref": "#/components/examples/list-mcp-servers-response" - } - } - } - } - }, - "list-operational-mcp-servers-success": { - "description": "成功获取用户托管MCP服务列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/McpServerOperationalListResponse" - }, - "examples": { - "list-operational-mcp-servers-response": { - "$ref": "#/components/examples/list-operational-mcp-servers-response" - } + "ListAgentIdentitiesData": { + "type": "object", + "description": "Agent 身份列表数据", + "properties": { + "agent_identities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentIdentityListItem" } + }, + "total_count": { + "type": "integer", + "format": "int64" + }, + "page_number": { + "type": "integer" + }, + "page_size": { + "type": "integer" } } }, - "get-mcp-server-success": { - "description": "成功获取MCP服务详情", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/McpServerDetailResponse" - }, - "examples": { - "get-mcp-server-response": { - "$ref": "#/components/examples/get-mcp-server-response" - } - } + "ResetKeyPairData": { + "type": "object", + "description": "重置密钥对返回数据", + "properties": { + "agent_id": { + "type": "string" + }, + "kid": { + "type": "string" + }, + "public_key": { + "$ref": "#/components/schemas/JWK" } } }, - "deploy-mcp-server-success": { - "description": "成功部署MCP服务", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/McpDeployResponse" - }, - "examples": { - "deploy-mcp-server-response": { - "$ref": "#/components/examples/deploy-mcp-server-response" - } - } + "AgentIdentityResponse": { + "type": "object", + "description": "Agent 身份详情响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/AgentIdentity" + }, + "message": { + "type": "string", + "examples": [ + "success" + ] + }, + "request_id": { + "type": "string" } } }, - "undeploy-mcp-server-success": { - "description": "成功解除MCP服务部署", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/McpUndeployResponse" - }, - "examples": { - "undeploy-mcp-server-response": { - "$ref": "#/components/examples/undeploy-mcp-server-response" - } - } + "ListAgentIdentitiesResponse": { + "type": "object", + "description": "Agent 身份列表响应", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "$ref": "#/components/schemas/ListAgentIdentitiesData" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } } }, - "create-studio-success": { - "description": "成功创建 Studio", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateStudioResponse" - }, - "examples": { - "create-studio-response": { - "$ref": "#/components/examples/create-studio-response" - } - } + "ResetKeyPairResponse": { + "type": "object", + "description": "重置密钥对响应", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "$ref": "#/components/schemas/ResetKeyPairData" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } } }, - "get-studio-success": { - "description": "成功获取 Studio 详情", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetStudioResponse" - }, - "examples": { - "get-studio-response": { - "$ref": "#/components/examples/get-studio-response" - } - } + "DeleteAgentIdentityResponse": { + "type": "object", + "description": "删除 Agent 身份响应", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } } }, - "update-studio-settings-success": { - "description": "成功更新 Studio 设置", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetStudioResponse" - }, - "examples": { - "update-studio-settings-response": { - "$ref": "#/components/examples/get-studio-response" - } - } + "PauseAgentResponse": { + "type": "object", + "description": "启用/停用 Agent 响应", + "properties": { + "success": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } } }, - "studio-operation-success": { - "description": "Studio 操作成功", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } + "TokenSignRequest": { + "type": "object", + "description": "Token 签发请求(Agent 用私钥签名 message=`agent_id|kid|audience|timestamp` 后提交)", + "required": [ + "agent_id", + "kid", + "audience", + "timestamp", + "signature" + ], + "properties": { + "agent_id": { + "type": "string", + "examples": [ + "agent_id:modelscope:agent_1234567890ab" + ] + }, + "kid": { + "type": "string", + "examples": [ + "agent-key-001" + ] + }, + "audience": { + "type": "string", + "description": "目标 Hub 应用的 client_id", + "examples": [ + "hub-app-abcdef" + ] + }, + "timestamp": { + "type": "integer", + "format": "int64", + "description": "签名时间戳(Unix 秒),需在服务器时间 ±5 分钟之内", + "examples": [ + 1717410000 + ] + }, + "signature": { + "type": "string", + "description": "Ed25519 签名 base64url 编码(无 padding)", + "examples": [ + "MEUCIQDxxx..." + ] } } }, - "studio-runtime-success": { - "description": "Studio 运行时操作成功", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StudioRuntimeResponse" - }, - "examples": { - "studio-runtime-response": { - "$ref": "#/components/examples/studio-runtime-response" - } - } - } - } - }, - "get-studio-logs-success": { - "description": "成功获取 Studio 日志", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StudioLogsResponse" - }, - "examples": { - "get-studio-logs-response": { - "$ref": "#/components/examples/get-studio-logs-response" - } - } - } - } - }, - "list-hardware-success": { - "description": "成功获取可用硬件配置列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListHardwareResponse" - } + "TokenSignData": { + "type": "object", + "description": "Token 签发返回数据", + "properties": { + "access_token": { + "type": "string", + "description": "签发的 JWT", + "examples": [ + "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0..." + ] + }, + "token_type": { + "type": "string", + "enum": [ + "Bearer" + ], + "examples": [ + "Bearer" + ] + }, + "expire_at": { + "type": "integer", + "format": "int64", + "description": "Token 失效时间(Unix 时间戳,单位秒)", + "examples": [ + 1717413600 + ] + }, + "jti": { + "type": "string", + "description": "JWT ID", + "examples": [ + "jti-1234567890" + ] } } }, - "list-sdk-versions-success": { - "description": "成功获取可用 SDK 版本列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListSDKVersionsResponse" - }, - "examples": { - "list-sdk-versions-response": { - "$ref": "#/components/examples/list-sdk-versions-response" - } - } + "TokenSignResponse": { + "type": "object", + "description": "Token 签发响应", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "$ref": "#/components/schemas/TokenSignData" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } } }, - "list-base-images-success": { - "description": "成功获取可用基础镜像列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListBaseImagesResponse" - } + "TokenRecordItem": { + "type": "object", + "description": "Token 签发记录项", + "properties": { + "token_id": { + "type": "string" + }, + "audience": { + "type": "string", + "description": "目标 Hub 的 client_id" + }, + "issued_at": { + "type": "string", + "description": "签发时间 (ISO 8601)" + }, + "expire_at": { + "type": "string", + "description": "过期时间 (ISO 8601)" + }, + "status": { + "type": "string", + "enum": [ + "active", + "expired", + "revoked" + ] + }, + "jwt": { + "type": "string", + "description": "JWT 字符串" } } }, - "list-studio-variables-success": { - "description": "成功获取明文变量列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListStudioVariablesResponse" + "ListTokenRecordsData": { + "type": "object", + "description": "Token 签发记录列表数据", + "properties": { + "token_records": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TokenRecordItem" } + }, + "total_count": { + "type": "integer", + "format": "int64" + }, + "page_number": { + "type": "integer" + }, + "page_size": { + "type": "integer" } } }, - "list-skills-success": { - "description": "成功获取技能列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SkillListResponse" - }, - "examples": { - "list-skills-response": { - "$ref": "#/components/examples/list-skills-response" - } - } + "ListTokenRecordsResponse": { + "type": "object", + "description": "Token 签发记录列表响应", + "properties": { + "success": { + "type": "boolean" + }, + "data": { + "$ref": "#/components/schemas/ListTokenRecordsData" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } } }, - "get-skill-success": { - "description": "成功获取技能详情", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SkillDetailResponse" - }, - "examples": { - "get-skill-response": { - "$ref": "#/components/examples/get-skill-response" - } - } + "AgentIdConfiguration": { + "type": "object", + "description": "Agent IDP OIDC Discovery 配置", + "properties": { + "issuer": { + "type": "string", + "examples": [ + "https://modelscope.cn" + ] + }, + "token_endpoint": { + "type": "string", + "examples": [ + "https://modelscope.cn/openapi/v1/agent_id/token" + ] + }, + "jwks_uri": { + "type": "string", + "examples": [ + "https://modelscope.cn/openapi/v1/agent_id/.well-known/agentid-jwks" + ] + }, + "registration_endpoint": { + "type": "string", + "examples": [ + "https://modelscope.cn/openapi/v1/agent_ids" + ] + }, + "activity_endpoint": { + "type": "string" + }, + "id_token_signing_alg_values_supported": { + "type": "string", + "examples": [ + "EdDSA" + ] } } }, - "create-skill-success": { - "description": "成功创建技能", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateSkillResponse" - }, - "examples": { - "create-skill-response": { - "$ref": "#/components/examples/create-skill-response" - } + "JWKS": { + "type": "object", + "description": "JSON Web Key Set", + "properties": { + "keys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JWK" } } } }, - "update-skill-settings-success": { - "description": "成功更新技能设置", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - }, - "examples": { - "update-skill-settings-response": { - "$ref": "#/components/examples/update-skill-settings-response" - } - } - } - } + "Collection": { + "type": "object", + "description": "Collection 详情对象", + "properties": { + "slug": { + "type": "string", + "description": "Collection 唯一标识,格式 {owner}/{slug}。新建为 {owner}/{title_slug};历史为 {owner}/{title_slug}-{short_id}", + "examples": [ + "iSolver/best-nlp-models-6a3b2c" + ] + }, + "title": { + "type": "string", + "description": "Collection 标题", + "examples": [ + "最佳 NLP 模型合集" + ] + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Collection 描述(Markdown)", + "examples": [ + "精选的自然语言处理模型..." + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "iSolver" + ] + }, + "visibility": { + "type": "string", + "description": "可见性:public(公开)/ private(私有)", + "enum": [ + "public", + "private" + ], + "examples": [ + "public" + ] + }, + "theme": { + "type": [ + "string", + "null" + ], + "description": "主题标签,枚举值:Blue/Pink/Purple/Cyan", + "examples": [ + "Blue" + ] + }, + "item_count": { + "type": "integer", + "description": "Collection 中条目数量", + "examples": [ + 12 + ] + }, + "likes": { + "type": "integer", + "description": "喜欢数", + "examples": [ + 128 + ] + }, + "view_count": { + "type": "integer", + "description": "浏览量", + "examples": [ + 1024 + ] + }, + "items": { + "type": "array", + "description": "Collection 中的条目列表", + "items": { + "$ref": "#/components/schemas/CollectionItem" + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "创建时间,ISO 8601 UTC", + "examples": [ + "2025-01-15T08:00:00Z" + ] + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "更新时间,ISO 8601 UTC", + "examples": [ + "2025-04-20T10:30:00Z" + ] + }, + "url": { + "type": "string", + "description": "Collection 页面 URL", + "examples": [ + "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + ] + }, + "install_command": { + "type": [ + "array", + "null" + ], + "description": "当 items 包含 skill 时,返回下载命令数组", + "items": { + "type": "string" + }, + "examples": [ + [ + "modelscope skill install @iSolver/my-skill" + ] + ] + } + }, + "required": [ + "slug", + "title", + "owner", + "visibility", + "item_count", + "likes", + "view_count", + "items", + "created_at", + "updated_at", + "url" + ] + }, + "CollectionItem": { + "type": "object", + "description": "Collection 条目对象。条目通过 item_type + item_object_id 唯一标识,不维护独立 item_id", + "properties": { + "item_type": { + "type": "string", + "description": "资源类型:model / dataset / studio / paper / skill / mcp", + "enum": [ + "model", + "dataset", + "studio", + "paper", + "skill", + "mcp" + ], + "examples": [ + "model" + ] + }, + "item_object_id": { + "type": "string", + "description": "资源标识,如 damo/nlp_bert_base 或论文 ID", + "examples": [ + "damo/nlp_bert_base" + ] + }, + "note": { + "type": [ + "string", + "null" + ], + "description": "用户对该条目的备注说明(Markdown)", + "examples": [ + "这个模型在中文 NLP 任务上表现优异" + ] + }, + "position": { + "type": "integer", + "description": "排序位置(从 1 开始)", + "examples": [ + 1 + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "添加时间,ISO 8601 UTC", + "examples": [ + "2025-01-15T08:00:00Z" + ] + }, + "visibility": { + "type": "string", + "description": "可见性:public / protected / private。protected 对应“仅公开体验”语义", + "enum": [ + "public", + "protected", + "private" + ], + "examples": [ + "public" + ] + }, + "gated": { + "type": "boolean", + "description": "visibility 为 public 时,是否需先申请并经同意后才可访问", + "examples": [ + false + ] + }, + "protected_mode": { + "type": "integer", + "description": "protected(仅公开体验)模式标识;用于表达条目/资源的仅公开体验语义,具体取值含义以产品后端实现为准(如 0=无、2=仅公开体验)", + "examples": [ + 0 + ] + } + }, + "required": [ + "item_type", + "item_object_id", + "position", + "created_at", + "visibility", + "gated", + "protected_mode" + ] + }, + "FailedCollectionItem": { + "type": "object", + "description": "批量操作中失败的条目及原因", + "properties": { + "item_type": { + "type": "string", + "description": "资源类型", + "examples": [ + "model" + ] + }, + "item_object_id": { + "type": "string", + "description": "资源标识", + "examples": [ + "not/exist-model" + ] + }, + "reason": { + "type": "string", + "description": "失败原因错误码,如 ResourceNotFound、ItemAlreadyInCollection、ItemNotInCollection、PermissionDenied", + "examples": [ + "ResourceNotFound" + ] + } + }, + "required": [ + "item_type", + "item_object_id", + "reason" + ] + }, + "CreateCollectionRequest": { + "type": "object", + "description": "创建 Collection 请求", + "properties": { + "title": { + "type": "string", + "maxLength": 128, + "description": "Collection 标题(最长 128 字符)", + "examples": [ + "最佳 NLP 模型合集" + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "iSolver" + ] + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Collection 描述(Markdown)", + "examples": [ + "精选的自然语言处理模型..." + ] + }, + "visibility": { + "type": "string", + "description": "可见性:public / private,默认 public", + "enum": [ + "public", + "private" + ], + "default": "public", + "examples": [ + "public" + ] + }, + "theme": { + "type": [ + "string", + "null" + ], + "description": "主题标签,枚举值:Blue/Pink/Purple/Cyan", + "examples": [ + "Blue" + ] + } + }, + "required": [ + "title", + "owner" + ] + }, + "UpdateCollectionRequest": { + "type": "object", + "description": "更新 Collection 请求。传入哪个字段即更新哪个字段,未传字段保持原值", + "properties": { + "title": { + "type": [ + "string", + "null" + ], + "maxLength": 128, + "description": "Collection 标题(最长 128 字符)", + "examples": [ + "最佳 NLP 模型合集(已更名)" + ] + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "Collection 描述(Markdown)", + "examples": [ + "更新后的描述..." + ] + }, + "owner": { + "type": [ + "string", + "null" + ], + "description": "所有者", + "examples": [ + "iSolver" + ] + }, + "visibility": { + "type": [ + "string", + "null" + ], + "description": "可见性:public / private", + "enum": [ + "public", + "private", + null + ], + "examples": [ + "private" + ] + }, + "theme": { + "type": [ + "string", + "null" + ], + "description": "主题标签,枚举值:Blue/Pink/Purple/Cyan", + "examples": [ + "Purple" + ] + } + } + }, + "CollectionResponse": { + "type": "object", + "description": "获取 Collection 详情的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "$ref": "#/components/schemas/Collection" + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "CollectionListResponse": { + "type": "object", + "description": "获取 Collection 列表的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "type": "object", + "description": "列表数据", + "properties": { + "collection_list": { + "type": "array", + "description": "Collection 列表,返回完整 Collection 对象", + "items": { + "$ref": "#/components/schemas/Collection" + } + }, + "total_count": { + "type": "integer", + "description": "符合筛选条件的合集总数", + "examples": [ + 128 + ] + }, + "page_number": { + "type": "integer", + "description": "当前页码", + "examples": [ + 1 + ] + }, + "page_size": { + "type": "integer", + "description": "每页大小", + "examples": [ + 10 + ] + } + }, + "required": [ + "collection_list", + "total_count", + "page_number", + "page_size" + ] + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "CollectionItemListResponse": { + "type": "object", + "description": "获取 Collection 条目列表的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "type": "object", + "description": "条目列表数据", + "properties": { + "items": { + "type": "array", + "description": "条目列表", + "items": { + "$ref": "#/components/schemas/CollectionItem" + } + }, + "total_count": { + "type": "integer", + "description": "条目总数", + "examples": [ + 50 + ] + }, + "page_number": { + "type": "integer", + "description": "当前页码", + "examples": [ + 1 + ] + }, + "page_size": { + "type": "integer", + "description": "每页大小", + "examples": [ + 10 + ] + } + }, + "required": [ + "items", + "total_count", + "page_number", + "page_size" + ] + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "CollectionItemRequest": { + "type": "object", + "description": "待添加条目", + "properties": { + "item_type": { + "type": "string", + "description": "资源类型:model / dataset / studio / paper / skill / mcp", + "enum": [ + "model", + "dataset", + "studio", + "paper", + "skill", + "mcp" + ], + "examples": [ + "model" + ] + }, + "item_object_id": { + "type": "string", + "description": "资源标识,如 damo/nlp_bert_base_chinese", + "examples": [ + "damo/nlp_bert_base_chinese" + ] + }, + "note": { + "type": [ + "string", + "null" + ], + "description": "备注(Markdown)", + "examples": [ + "推荐用于中文文本分类" + ] + }, + "position": { + "type": [ + "integer", + "null" + ], + "description": "排序位置(从 1 开始)", + "examples": [ + 1 + ] + } + }, + "required": [ + "item_type", + "item_object_id" + ] + }, + "AddCollectionItemsRequest": { + "type": "object", + "description": "批量添加条目请求", + "properties": { + "items": { + "type": "array", + "description": "待添加条目列表(最少 1 个元素)", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/CollectionItemRequest" + } + } + }, + "required": [ + "items" + ] + }, + "AddCollectionItemsResponse": { + "type": "object", + "description": "批量添加条目的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true(部分失败仍为 true,失败项见 failed_items)", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "type": "object", + "description": "添加结果", + "properties": { + "added_count": { + "type": "integer", + "description": "成功添加的条目数量", + "examples": [ + 2 + ] + }, + "url": { + "type": "string", + "description": "Collection 页面 URL", + "examples": [ + "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + ] + }, + "failed_items": { + "type": "array", + "description": "添加失败的条目及原因", + "items": { + "$ref": "#/components/schemas/FailedCollectionItem" + } + } + }, + "required": [ + "added_count", + "url", + "failed_items" + ] + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "UpdateCollectionItemRequest": { + "type": "object", + "description": "更新单个条目请求。传入哪个字段即更新哪个字段", + "properties": { + "note": { + "type": [ + "string", + "null" + ], + "description": "备注(Markdown)", + "examples": [ + "更新后的备注" + ] + }, + "position": { + "type": [ + "integer", + "null" + ], + "description": "排序位置(从 1 开始)", + "examples": [ + 2 + ] + } + } + }, + "UpdateCollectionItemResponse": { + "type": "object", + "description": "更新单个条目的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "$ref": "#/components/schemas/CollectionItem" + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "CreateGalleryRequest": { + "type": "object", + "description": "创建 Gallery 请求", + "properties": { + "name": { + "type": "string", + "maxLength": 128, + "pattern": "^[0-9a-zA-Z_.-]+$", + "description": "Gallery 名称,最长 128 字符,仅允许字母、数字、下划线、连字符、点", + "examples": [ + "my-gallery" + ] + }, + "owner": { + "type": "string", + "maxLength": 128, + "description": "所属用户或组织名,最长 128 字符", + "examples": [ + "my-org" + ] + }, + "files": { + "type": "array", + "maxItems": 50, + "description": "文件 ID 列表(文件上传接口返回的 ID),最多 50 个,单个元素不能为空", + "items": { + "type": "string", + "minLength": 1 + }, + "examples": [ + [ + "8c378570-8991-431b-a82c-96f3d0b4f0f4" + ] + ] + }, + "entry_file": { + "type": "string", + "minLength": 1, + "description": "入口文件的相对路径,对应 files 中某个已上传文件;后缀须与 category 匹配(后缀不区分大小写):notebook→.ipynb,website→.html,pdf→.pdf(file 类别不限后缀)", + "examples": [ + "notebook/demo.ipynb" + ] + }, + "private": { + "type": "boolean", + "default": false, + "description": "是否私有,默认 false(公开)", + "examples": [ + false + ] + }, + "category": { + "type": "string", + "description": "类型(必传),可选值:notebook / website / pdf / file;除 file 外须与 entry_file 后缀匹配(后缀不区分大小写):notebook→.ipynb,website→.html,pdf→.pdf", + "enum": [ + "notebook", + "website", + "pdf", + "file" + ], + "examples": [ + "notebook" + ] + }, + "labels": { + "type": [ + "array", + "null" + ], + "maxItems": 10, + "description": "标签列表,最多 10 个,单个标签最长 20 字符", + "items": { + "type": "string", + "maxLength": 20 + }, + "examples": [ + [ + "demo", + "tutorial" + ] + ] + }, + "path": { + "type": [ + "string", + "null" + ], + "maxLength": 128, + "pattern": "^[0-9a-zA-Z_-]+$", + "description": "自定义 URL 尾缀(选填),仅允许字母、数字、下划线、连字符,最长 128 字符;不填则不生成,填写后灵感流对应 URL:https://modelscope.cn/gallery/{owner}/{path}", + "examples": [ + "my-custom-path" + ] + } + }, + "required": [ + "name", + "owner", + "files", + "entry_file", + "category" + ] + }, + "CreateGalleryResponse": { + "type": "object", + "description": "创建 Gallery 的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "req-xxxxxx" + ] + }, + "data": { + "type": "object", + "description": "创建结果", + "properties": { + "id": { + "type": "string", + "description": "Gallery 唯一标识(UUID)", + "examples": [ + "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + ] + }, + "url": { + "type": "string", + "description": "Gallery 访问链接", + "examples": [ + "https://modelscope.cn/gallery/my-org/a1b2c3d4-e5f6-7890-abcd-ef1234567890" + ] + } + }, + "required": [ + "id", + "url" + ] + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "BatchUpdateCollectionItem": { + "type": "object", + "description": "批量更新条目。通过 item_type + item_object_id 定位,传入哪个字段就修改哪个字段", + "properties": { + "item_type": { + "type": "string", + "description": "资源类型,用于定位", + "enum": [ + "model", + "dataset", + "studio", + "paper", + "skill", + "mcp" + ], + "examples": [ + "model" + ] + }, + "item_object_id": { + "type": "string", + "description": "资源标识,用于定位", + "examples": [ + "damo/nlp_bert_base_chinese" + ] + }, + "note": { + "type": [ + "string", + "null" + ], + "description": "备注(Markdown),不传则不修改", + "examples": [ + "更新后的备注" + ] + }, + "position": { + "type": [ + "integer", + "null" + ], + "description": "排序位置(从 1 开始),不传则不修改", + "examples": [ + 1 + ] + } + }, + "required": [ + "item_type", + "item_object_id" + ] + }, + "UpdateCollectionItemsRequest": { + "type": "object", + "description": "批量更新条目请求", + "properties": { + "items": { + "type": "array", + "description": "待更新条目列表(最少 1 个元素)", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/BatchUpdateCollectionItem" + } + } + }, + "required": [ + "items" + ] + }, + "UpdateCollectionItemsResponse": { + "type": "object", + "description": "批量更新条目的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true(部分失败仍为 true,失败项见 failed_items)", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "type": "object", + "description": "批量更新结果", + "properties": { + "updated_count": { + "type": "integer", + "description": "成功更新的条目数量", + "examples": [ + 2 + ] + }, + "failed_items": { + "type": "array", + "description": "更新失败的条目及原因", + "items": { + "$ref": "#/components/schemas/FailedCollectionItem" + } + } + }, + "required": [ + "updated_count", + "failed_items" + ] + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "AgentPlaygroundModelSetupRef": { + "type": "object", + "description": "模型引用。创建实例的 `models`、导入的 `models` 与下发模型接口共用。\n\n三种引用形式,按优先级解析:\n\n- 按名称(推荐):`provider_name` + `model_name`\n- 按供应商 ID:`provider_id` + `model_name`\n- 按模型 ID:`model_id`\n\n供应商只在「调用者本人创建的供应商 + 平台内置供应商」范围内查找,引用他人资源返回 403。\n平台内置供应商目前只有 `ModelScope API-Inference`,使用时 `model_name` 直接作为\nAPI-Inference 的模型 ID。\n", + "properties": { + "provider_name": { + "type": "string", + "description": "模型供应商名称;传了它就按名称解析,此时 `model_name` 必填", + "examples": [ + "ModelScope API-Inference" + ] + }, + "model_name": { + "type": "string", + "description": "模型名称,也可写作 `model`", + "examples": [ + "Qwen/Qwen3.5-27B" + ] + }, + "provider_id": { + "type": "string", + "description": "模型供应商 UUID(自定义供应商记录的 uuid),与实例内 Agent 信息里的 `provider_id` 不是同一个东西", + "examples": [ + "4d32e63f-6f7b-11f1-b963-00163e27a6e0" + ] + }, + "model_id": { + "type": "string", + "description": "模型记录 UUID,也可写作 `uuid`", + "examples": [ + "0a1f5c7e-3b2d-4a68-9f0c-5d7e1b2a3c4d" + ] + } + } + }, + "AgentPlaygroundMcpServerConfig": { + "type": "object", + "description": "MCP 连接配置,校验通过后原样下发给实例。\n\n- 可以是裸配置(`{\"type\": \"sse\", \"url\": \"...\"}`、`{\"command\": \"npx\", \"args\": [...]}`),\n 也可以是 `{\"mcpServers\": {\"\": {...}}}` 包装形;包装形必须恰好包含 1 个条目。\n- `url` 与 `command` 必须有且只有一个非空;`url` 必须公网可达,指向内网或保留地址会被拒绝。\n- 带 `command` 的配置按 stdio 处理;带 `url` 且未写 `type` 时按 SSE 处理,\n Streamable HTTP 服务必须显式写 `\"type\": \"streamable_http\"`。\n", + "additionalProperties": true, + "examples": [ + { + "mcpServers": { + "amap-maps": { + "type": "sse", + "url": "https://mcp.example.com/sse" + } + } + } + ] + }, + "AgentPlaygroundMcpSetupRef": { + "type": "object", + "description": "MCP 引用。创建实例的 `mcps`、导入的 `mcps` 与下发 MCP 接口共用。", + "properties": { + "name": { + "type": "string", + "description": "实例内 MCP client key", + "examples": [ + "amap-maps" + ] + }, + "source": { + "type": "string", + "description": "来源标记,只作记录,大小写不敏感;传其他值返回 400", + "enum": [ + "ms-platform", + "ms-deployed", + "custom" + ], + "examples": [ + "custom" + ] + }, + "server_config": { + "$ref": "#/components/schemas/AgentPlaygroundMcpServerConfig" + } + }, + "required": [ + "name", + "server_config" + ] + }, + "AgentPlaygroundInstance": { + "type": "object", + "description": "实验场实例。\n\n`agent_count` / `model_count` / `mcp_count` / `skill_count` 为统计值,可能落后于最近一次下发或导入。\n", + "properties": { + "uuid": { + "type": "string", + "description": "实例 ID", + "examples": [ + "51a7d9d2-a521-451a-baf5-367cb115a29a" + ] + }, + "owner": { + "type": "string", + "description": "实例归属账号,即调用者本人;暂不支持组织", + "examples": [ + "username" + ] + }, + "instance_name": { + "type": "string", + "description": "实例名称,在本人名下唯一", + "examples": [ + "my-agent" + ] + }, + "display_name": { + "type": "string", + "description": "展示名称,可为空串", + "examples": [ + "我的 Agent" + ] + }, + "description": { + "type": "string", + "description": "实例描述,可为空串", + "examples": [ + "用于测试的 Agent Playground 实例" + ] + }, + "logo": { + "type": "string", + "description": "头像图片 URL,未设置时为空串", + "examples": [ + "https://resources.modelscope.cn/avatar/my-agent.png" + ] + }, + "status": { + "type": "string", + "description": "实例状态:\n\n- `starting`:创建、唤醒、重启或导入内容应用中\n- `running`:运行中\n- `sleeping`:已休眠\n- `failed`:运行异常。不是终态,平台会继续尝试恢复,恢复后自动回到 `running`;\n 也可以直接调用重启 / 暂停 / 删除\n", + "enum": [ + "starting", + "running", + "sleeping", + "failed" + ], + "examples": [ + "running" + ] + }, + "framework_type": { + "type": "string", + "description": "框架类型,取值以平台配置为准,线上为 `qwenpaw` / `ms-agent`", + "examples": [ + "qwenpaw" + ] + }, + "source": { + "type": "string", + "description": "创建来源:`playground`(普通创建)/ `agent_hub`(从 Agent Hub 创建)", + "examples": [ + "playground" + ] + }, + "framework_version": { + "type": "string", + "description": "实例当前的框架版本", + "examples": [ + "2.2.0" + ] + }, + "agent_count": { + "type": "integer", + "description": "实例内 Agent 数", + "examples": [ + 2 + ] + }, + "model_count": { + "type": "integer", + "description": "模型数,跨 Agent 去重", + "examples": [ + 1 + ] + }, + "mcp_count": { + "type": "integer", + "description": "MCP 数,跨 Agent 去重", + "examples": [ + 3 + ] + }, + "skill_count": { + "type": "integer", + "description": "Skill 数,跨 Agent 去重", + "examples": [ + 17 + ] + }, + "gmt_created": { + "type": "string", + "format": "date-time", + "description": "创建时间,RFC 3339、UTC+8、秒级", + "examples": [ + "2026-09-17T15:47:19+08:00" + ] + }, + "gmt_modified": { + "type": "string", + "format": "date-time", + "description": "最后更新时间,由系统维护,不代表用户操作", + "examples": [ + "2026-09-18T17:06:19+08:00" + ] + }, + "user_modified_at": { + "type": "string", + "format": "date-time", + "description": "用户最近一次操作时间(创建 / 改名 / 暂停 / 恢复 / 重启 / 导入),实例列表默认按它倒序;自动休眠不会更新它", + "examples": [ + "2026-09-17T15:47:19+08:00" + ] + } + } + }, + "AgentPlaygroundAgentModel": { + "type": "object", + "description": "实例内 Agent 可用的模型", + "properties": { + "provider_id": { + "type": "string", + "description": "实例内的供应商标识,与模型引用里的供应商名称 / UUID 不通用,不能回填给下发接口", + "examples": [ + "playground-openai-3f2a9c1d" + ] + }, + "provider": { + "type": "string", + "description": "供应商展示名", + "examples": [ + "Playground Openai (3f2a9c1d)" + ] + }, + "model": { + "type": "string", + "description": "模型 ID", + "examples": [ + "Qwen/Qwen3.5-27B" + ] + }, + "type": { + "type": "string", + "description": "协议类型", + "enum": [ + "openai", + "anthropic" + ], + "examples": [ + "openai" + ] + }, + "base_url": { + "type": "string", + "description": "模型服务地址", + "examples": [ + "https://api-inference.modelscope.cn/v1" + ] + }, + "enabled": { + "type": "boolean", + "description": "是否为当前生效模型;其余为已配置可切换的模型", + "examples": [ + true + ] + } + } + }, + "AgentPlaygroundPersonaFile": { + "type": "object", + "description": "persona 文件内容", + "properties": { + "content": { + "type": "string", + "description": "文件内容" + }, + "enabled": { + "type": "boolean", + "description": "该文件是否进入系统提示词", + "examples": [ + true + ] + } + } + }, + "AgentPlaygroundAgentSkill": { + "type": "object", + "description": "实例内已安装的 Skill", + "properties": { + "name": { + "type": "string", + "description": "Skill 名称", + "examples": [ + "web-search" + ] + }, + "description": { + "type": "string", + "description": "Skill 描述" + }, + "enabled": { + "type": "boolean", + "description": "是否启用", + "examples": [ + true + ] + }, + "source": { + "type": "string", + "description": "来源标记", + "examples": [ + "builtin" + ] + } + } + }, + "AgentPlaygroundMcpTool": { + "type": "object", + "description": "MCP 客户端暴露的工具", + "properties": { + "name": { + "type": "string", + "description": "工具名称", + "examples": [ + "maps_geo" + ] + }, + "description": { + "type": "string", + "description": "工具描述" + } + } + }, + "AgentPlaygroundMcpClient": { + "type": "object", + "description": "实例内已配置的 MCP 客户端", + "properties": { + "key": { + "type": "string", + "description": "实例内 MCP client key", + "examples": [ + "amap-maps" + ] + }, + "name": { + "type": "string", + "description": "MCP 名称", + "examples": [ + "amap-maps" + ] + }, + "enabled": { + "type": "boolean", + "description": "是否启用", + "examples": [ + true + ] + }, + "transport": { + "type": "string", + "description": "连接方式:`sse` / `streamable_http` / `stdio`", + "examples": [ + "sse" + ] + }, + "url": { + "type": "string", + "description": "连接地址;stdio 类型为启动命令", + "examples": [ + "https://mcp.example.com/sse" + ] + }, + "tools": { + "type": [ + "array", + "null" + ], + "description": "工具列表;仅对启用的客户端返回,ms-agent 恒为空数组", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundMcpTool" + } + } + } + }, + "AgentPlaygroundAgentSummary": { + "type": "object", + "description": "实例内 Agent 概要", + "properties": { + "id": { + "type": "string", + "description": "实例内 Agent 的 id,下发接口用它区分不同 Agent", + "examples": [ + "default" + ] + }, + "name": { + "type": "string", + "description": "Agent 名称", + "examples": [ + "Default Agent" + ] + }, + "description": { + "type": "string", + "description": "Agent 描述" + }, + "enabled": { + "type": "boolean", + "description": "Agent 是否启用", + "examples": [ + true + ] + }, + "models": { + "type": [ + "array", + "null" + ], + "description": "该 Agent 可用的模型", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundAgentModel" + } + } + } + }, + "AgentPlaygroundAgentDetail": { + "type": "object", + "description": "实例内 Agent 详情;qwenpaw 下某个 Agent 的详情拉取失败时为 null", + "properties": { + "id": { + "type": "string", + "description": "实例内 Agent 的 id", + "examples": [ + "default" + ] + }, + "name": { + "type": "string", + "description": "Agent 名称", + "examples": [ + "Default Agent" + ] + }, + "description": { + "type": "string", + "description": "Agent 描述" + }, + "models": { + "type": [ + "array", + "null" + ], + "description": "该 Agent 可用的模型", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundAgentModel" + } + }, + "persona": { + "type": "object", + "description": "persona 文件映射:key 为文件名(如 `AGENTS.md`,大小写敏感),value 为 `{content, enabled}`。\n", + "additionalProperties": { + "$ref": "#/components/schemas/AgentPlaygroundPersonaFile" + } + }, + "skills": { + "type": [ + "array", + "null" + ], + "description": "已安装的 Skill", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundAgentSkill" + } + }, + "mcp_clients": { + "type": [ + "array", + "null" + ], + "description": "已配置的 MCP 客户端", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundMcpClient" + } + } + } + }, + "AgentPlaygroundAgentItem": { + "type": "object", + "description": "实例内单个 Agent 的概要与详情", + "properties": { + "summary": { + "$ref": "#/components/schemas/AgentPlaygroundAgentSummary" + }, + "detail": { + "oneOf": [ + { + "$ref": "#/components/schemas/AgentPlaygroundAgentDetail" + }, + { + "type": "null" + } + ], + "description": "Agent 详情;详情拉取失败时为 null(summary 仍返回)" + } + } + }, + "AgentPlaygroundNotification": { + "type": "object", + "description": "实例一次性通知。拉取即消费,同一条通知只返回一次;保留 7 天。", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "通知 ID", + "examples": [ + 7 + ] + }, + "kind": { + "type": "string", + "description": "通知类型:\n\n- `import_result`:创建或导入的内容应用完成(未附带 message)\n- `conversation`:内容应用完成且附带了一句话,payload 含会话信息\n- `agent_import`:从 Agent Hub 导入 Agent 完成\n- `init_error`:某一项内容被跳过,实例仍会 running\n- `framework_update_error`:框架版本更新失败\n", + "enum": [ + "import_result", + "conversation", + "agent_import", + "init_error", + "framework_update_error" + ], + "examples": [ + "conversation" + ] + }, + "payload": { + "type": "object", + "description": "原样 JSON,键名不做转换。其中 `conversation` / `agent` 部分不同框架结构不同,\n用 `framework` 字段区分。\n", + "additionalProperties": true + }, + "gmt_created": { + "type": "string", + "format": "date-time", + "description": "通知产生时间,RFC 3339、UTC+8、秒级", + "examples": [ + "2026-07-12T15:32:47+08:00" + ] + } + } + }, + "CreateAgentPlaygroundInstanceRequest": { + "type": "object", + "description": "创建实例请求", + "properties": { + "instance_name": { + "type": "string", + "maxLength": 128, + "description": "实例名称,去掉首尾空白后非空,本人名下唯一(冲突返回 409)", + "examples": [ + "my-agent" + ] + }, + "display_name": { + "type": "string", + "maxLength": 256, + "description": "展示名称,缺省为空串", + "examples": [ + "我的 Agent" + ] + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "实例描述", + "examples": [ + "用于测试的 Agent Playground 实例" + ] + }, + "logo": { + "type": "string", + "maxLength": 512, + "description": "头像图片 URL,超长返回 400", + "examples": [ + "https://resources.modelscope.cn/avatar/my-agent.png" + ] + }, + "framework_type": { + "type": "string", + "description": "框架类型,须为平台当前配置的框架,线上为 `qwenpaw` / `ms-agent`", + "examples": [ + "qwenpaw" + ] + }, + "owner": { + "type": "string", + "description": "实例归属账号,缺省为调用者;只能填调用者本人,填其他账号或组织返回 403", + "examples": [ + "username" + ] + }, + "models": { + "type": "array", + "description": "就绪后自动下发的模型,依次设为生效模型,最后一个生效。\n\n**不传时会自动配置平台默认模型。**\n", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundModelSetupRef" + } + }, + "mcps": { + "type": "array", + "description": "就绪后自动下发的 MCP", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundMcpSetupRef" + } + }, + "skills": { + "type": "array", + "description": "就绪后自动下发的 Skill Hub 引用,格式 `path/name`;私有 Skill 需调用者有读权限", + "items": { + "type": "string" + }, + "examples": [ + [ + "@modelscope/web-search" + ] + ] + }, + "message": { + "type": "string", + "description": "附带的一句话:内容全部就位后用它对 `default` Agent 发起对话,会话信息经一次性通知返回(kind=`conversation`)", + "examples": [ + "帮我查一下明天的天气" + ] + } + }, + "required": [ + "instance_name", + "framework_type" + ] + }, + "CreateAgentPlaygroundInstanceFromAgentHubRequest": { + "type": "object", + "description": "从 Agent Hub 创建实例请求", + "properties": { + "instance_name": { + "type": "string", + "maxLength": 128, + "description": "实例名称,本人名下唯一", + "examples": [ + "playground-instance" + ] + }, + "display_name": { + "type": "string", + "maxLength": 256, + "description": "展示名称", + "examples": [ + "Playground Instance" + ] + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "实例描述", + "examples": [ + "从 Agent Hub 导入的实例" + ] + }, + "logo": { + "type": "string", + "maxLength": 512, + "description": "头像图片 URL" + }, + "framework_type": { + "type": "string", + "description": "框架类型;内容同步目前只支持 `qwenpaw` / `ms-agent`", + "examples": [ + "qwenpaw" + ] + }, + "owner": { + "type": "string", + "description": "实例归属账号,缺省为调用者" + }, + "agent_owner": { + "type": "string", + "description": "Agent Hub 源 agent 的归属账号", + "examples": [ + "jakexiao" + ] + }, + "agent_name": { + "type": "string", + "description": "Agent Hub 源 agent 的名称", + "examples": [ + "FirstAgent" + ] + }, + "models": { + "type": "array", + "description": "内容同步完成后下发的模型。\n\n本接口**不会**自动配置默认模型:不传时实例内模型取决于源 agent 的内容,可能为空。\n", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundModelSetupRef" + } + }, + "mcps": { + "type": "array", + "description": "内容同步完成后下发的 MCP", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundMcpSetupRef" + } + }, + "skills": { + "type": "array", + "description": "内容同步完成后下发的 Skill Hub 引用,格式 `path/name`", + "items": { + "type": "string" + } + }, + "message": { + "type": "string", + "description": "附带的一句话,内容就位后用它对 `default` Agent 发起对话" + } + }, + "required": [ + "instance_name", + "framework_type", + "agent_owner", + "agent_name" + ] + }, + "UpdateAgentPlaygroundInstanceRequest": { + "type": "object", + "description": "更新实例元信息请求。只允许修改这四个字段,`framework_type`、`owner` 与 init 内容都不能通过本接口修改。\n", + "properties": { + "instance_name": { + "type": "string", + "maxLength": 128, + "description": "实例名称,非空,本人名下唯一(冲突返回 409)", + "examples": [ + "my-agent-renamed" + ] + }, + "display_name": { + "type": "string", + "maxLength": 256, + "description": "展示名称;整体覆盖语义,缺省或传空串都会清空", + "examples": [ + "My Agent Renamed" + ] + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "实例描述;缺省或传空串都会清空", + "examples": [ + "Updated description" + ] + }, + "logo": { + "type": "string", + "maxLength": 512, + "description": "头像图片 URL;与其他字段不同,缺省保持原值,显式传入则覆盖(空串清空)", + "examples": [ + "https://resources.modelscope.cn/avatar/my-agent.png" + ] + } + }, + "required": [ + "instance_name" + ] + }, + "ImportToAgentPlaygroundInstanceRequest": { + "type": "object", + "description": "向已有实例追加导入 model / mcp / skill 的请求。四者至少传一个,否则返回 400。\n导入内容恒作用于实例内 `default` Agent。\n", + "properties": { + "models": { + "type": "array", + "description": "追加下发的模型", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundModelSetupRef" + } + }, + "mcps": { + "type": "array", + "description": "追加下发的 MCP", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundMcpSetupRef" + } + }, + "skills": { + "type": "array", + "description": "追加下发的 Skill Hub 引用,格式 `path/name`;私有 Skill 需调用者有读权限", + "items": { + "type": "string" + }, + "examples": [ + [ + "@modelscope/web-search" + ] + ] + }, + "message": { + "type": "string", + "description": "本批内容就位后用它对 `default` Agent 发起对话", + "examples": [ + "帮我查一下明天的天气" + ] + } + } + }, + "SetupAgentPlaygroundModelRequest": { + "type": "object", + "description": "下发模型配置请求。在模型引用(`provider_name` + `model_name` 等)之外额外支持 `agent_id`。\n\n下发后该模型即目标 Agent 的生效模型;重复下发同一个模型是幂等的。\n", + "allOf": [ + { + "$ref": "#/components/schemas/AgentPlaygroundModelSetupRef" + }, + { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "实例内目标 Agent 的 id,取自实例内 Agent 信息接口的 `detail.id`,缺省 `default`", + "examples": [ + "default" + ] + } + } + } + ] + }, + "SetupAgentPlaygroundMcpRequest": { + "type": "object", + "description": "下发 MCP 配置请求。仅新增:目标 Agent 下已有同名 client key 时返回 409,不会覆盖。\n", + "allOf": [ + { + "$ref": "#/components/schemas/AgentPlaygroundMcpSetupRef" + }, + { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "实例内目标 Agent 的 id,缺省 `default`", + "examples": [ + "default" + ] + } + } + } + ] + }, + "SetupAgentPlaygroundSkillRequest": { + "type": "object", + "description": "下发 Skill 配置请求。按 Skill Hub 引用把 Skill 安装到目标 Agent。\n\n仅新增:实例内已有同名 Skill 返回 409,不会覆盖。\n", + "properties": { + "agent_id": { + "type": "string", + "description": "实例内目标 Agent 的 id,缺省 `default`", + "examples": [ + "default" + ] + }, + "skill_ref": { + "type": "string", + "description": "Skill Hub 引用,格式 `path/name`(按最后一个 `/` 拆分,path 可带 `@` 前缀)", + "examples": [ + "@modelscope/web-search" + ] + } + }, + "required": [ + "skill_ref" + ] + }, + "AgentPlaygroundInstanceResponse": { + "type": "object", + "description": "单个实例响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "examples": [ + "7d3f8f8a-8c1f-4d0b-8f1a-7b6a0d8a1a11" + ] + }, + "data": { + "$ref": "#/components/schemas/AgentPlaygroundInstance" + } + }, + "required": [ + "success", + "request_id" + ] + }, + "AgentPlaygroundInstanceListResponse": { + "type": "object", + "description": "实例列表响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "examples": [ + "bb971b6e-36f0-4c4c-a50e-ee3fb6d3ceba" + ] + }, + "data": { + "type": "object", + "properties": { + "instances": { + "type": [ + "array", + "null" + ], + "description": "实例列表", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundInstance" + } + }, + "total_count": { + "type": "integer", + "format": "int64", + "description": "总记录数", + "examples": [ + 5 + ] + }, + "page_number": { + "type": "integer", + "description": "当前页码", + "examples": [ + 1 + ] + }, + "page_size": { + "type": "integer", + "description": "每页条数", + "examples": [ + 10 + ] + } + } + } + }, + "required": [ + "success", + "request_id" + ] + }, + "DeleteAgentPlaygroundInstanceResponse": { + "type": "object", + "description": "删除实例响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "examples": [ + "0d2b7a54-1f6c-4a2b-9e33-2c7f9c1d8b40" + ] + }, + "data": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean", + "description": "恒为 true(幂等:对不存在或删除中的实例同样返回)", + "examples": [ + true + ] + } + } + } + }, + "required": [ + "success", + "request_id" + ] + }, + "AgentPlaygroundAgentListResponse": { + "type": "object", + "description": "实例内 Agent 信息响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "examples": [ + "3c9a1b2d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" + ] + }, + "data": { + "type": "object", + "properties": { + "agents": { + "type": [ + "array", + "null" + ], + "description": "实例内 Agent 列表;实例未就绪且无可用数据时为空列表", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundAgentItem" + } + }, + "refreshed_at": { + "type": "integer", + "format": "int64", + "description": "这批数据的更新时间(Unix 秒),`0` 表示还没有取到过", + "examples": [ + 1789722407 + ] + } + } + } + }, + "required": [ + "success", + "request_id" + ] + }, + "AgentPlaygroundNotificationListResponse": { + "type": "object", + "description": "实例一次性通知响应", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "examples": [ + "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b" + ] + }, + "data": { + "type": "object", + "properties": { + "notifications": { + "type": [ + "array", + "null" + ], + "description": "未读通知列表,按产生顺序返回;返回后即消费", + "items": { + "$ref": "#/components/schemas/AgentPlaygroundNotification" + } + } + } + } + }, + "required": [ + "success", + "request_id" + ] + }, + "UserPublic": { + "type": "object", + "description": "用户公开信息", + "properties": { + "username": { + "type": "string", + "description": "用户名", + "examples": [ + "tester" + ] + }, + "nickname": { + "type": "string", + "description": "昵称", + "examples": [ + "测试用户" + ] + }, + "description": { + "type": "string", + "description": "描述", + "examples": [ + "这是一个测试用户" + ] + }, + "avatar_url": { + "type": "string", + "format": "uri", + "description": "头像图片URL地址", + "examples": [ + "https://resouces.modelscope.cn/avatar/516aa51f-5fb7-49eb-930c-e90500ae13ca.png" + ] + } + }, + "required": [ + "username" + ] + }, + "UserPrivate": { + "type": "object", + "description": "用户私有信息(仅自己可见)", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "邮箱地址", + "examples": [ + "test@modelscope.cn" + ] + } + } + }, + "CreateModelRequest": { + "type": "object", + "description": "创建通用模型仓库请求参数。本接口仅接收通用元数据字段,创建成功后 `model_category` 默认为 `base`。专区业务字段请通过 `PUT /models/{owner}/{repo_name}/category` 设置。", + "additionalProperties": false, + "properties": { + "task": { + "type": [ + "string", + "null" + ], + "description": "模型任务,取 GET /models/options 的 tasks[].id,例如 text-generation。省略、null 或空字符串表示不设置;未知任务返回 HTTP 400。", + "examples": [ + "text-generation" + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "alice", + "my-org" + ] + }, + "repo_name": { + "type": "string", + "description": "仓库英文 ID,仅允许字母、数字、点、连字符和下划线,创建后不可修改", + "maxLength": 128, + "pattern": "^[a-zA-Z0-9._-]+$", + "examples": [ + "my-model", + "qwen-7b-finetuned" + ] + }, + "display_name": { + "type": "string", + "description": "模型展示名称(中英文均可),用于 UI 展示", + "maxLength": 128, + "examples": [ + "我的大语言模型" + ] + }, + "license": { + "type": "string", + "description": "创建模型支持 /api/v1/licenses 返回的 19 个许可证名称及下列对应标识符(均原样保存、区分大小写);省略或空字符串默认 apache-2.0。未列出的值返回 HTTP 400,other 必须提供 license_name。", + "enum": [ + "", + "apache-2.0", + "Apache License 2.0", + "gpl-2.0", + "GPL-2.0", + "lgpl-3.0", + "LGPL-3.0", + "mit", + "MIT License", + "lgpl-2.1", + "LGPL-2.1", + "gpl-3.0", + "GPL-3.0", + "afl-3.0", + "AFL-3.0", + "ecl-2.0", + "ECL-2.0", + "cc-by-4.0", + "CC-BY-4.0", + "creativeml-openrail-m", + "cc-by-nc-nd", + "CC-BY-NC-ND", + "agpl-3.0", + "cc-by-nc-4.0", + "CC-BY-NC-4.0", + "cc-by-nc-sa-4.0", + "CC-BY-NC-SA-4.0", + "odc-by-1.0", + "ODC-BY-1.0", + "cc-by-sa-4.0", + "CC-BY-SA-4.0", + "cc0-1.0", + "CC0-1.0", + "bsd-3-clause", + "BSD-3-Clause", + "other" + ], + "default": "apache-2.0", + "examples": [ + "apache-2.0", + "mit", + "other" + ] + }, + "license_name": { + "type": "string", + "description": "自定义许可证名称。license=\"other\" 时必填,其他许可证下忽略" + }, + "license_link": { + "type": "string", + "description": "【可选】自定义许可证链接,仅当 license=\"other\" 时生效。留空时仓库将自动在根目录创建 LICENSE.md 空白文件", + "format": "uri" + }, + "visibility": { + "type": "string", + "description": "仓库可见性,默认 public。protected 仅适用于 Studio,创建模型时不接受该值", + "enum": [ + "public", + "private", + "gated" + ], + "default": "public" + }, + "gated_mode": { + "description": "创建时仅 visibility=\"gated\" 可传 gated_mode:auto 自动审批,manual 人工审批;省略或空字符串默认 auto。null、布尔值、数字及其他非法值返回 400。visibility 为 public/private(含省略后默认 public)时禁止传 gated_mode。请求仅接受文档列出的字段,未知字段返回 400。", + "type": "string", + "enum": [ + "auto", + "manual", + "" + ] + }, + "description": { + "type": "string", + "description": "模型简介", + "maxLength": 2000, + "examples": [ + "基于 Qwen 微调的对话模型" + ] + } + }, + "required": [ + "owner", + "repo_name" + ] + }, + "ModelCreated": { + "type": "object", + "description": "创建成功后返回的模型仓库核心信息", + "properties": { + "id": { + "type": "string", + "description": "模型仓库唯一标识(格式:owner/repo_name)", + "examples": [ + "alice/my-model" + ] + }, + "repo_name": { + "type": "string", + "description": "仓库英文 ID", + "examples": [ + "my-model" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ], + "description": "模型展示名称", + "examples": [ + "我的大语言模型" + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "alice" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "模型平台访问地址", + "examples": [ + "https://modelscope.cn/models/alice/my-model" + ] + } + }, + "required": [ + "id", + "repo_name", + "owner", + "url" + ] + }, + "CreateModelResponse": { + "type": "object", + "description": "创建模型标准响应包裹格式", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/ModelCreated" + }, + "request_id": { + "type": "string", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "ModelCategory": { + "type": "string", + "description": "模型大类。POST /models 创建后默认为 base;设置/变更专区类型请调用 PUT /models/{owner}/{repo_name}/category。\n- base: 通用模型,无专区业务字段;传入 base 可回退为通用模型\n- aigc: AIGC 专区模型,需填写 aigc_type / base_model_architecture_type / base_model_architecture_subtype / version_name 等\n- science: 科学模型专区,需填写 primary_subject_area / secondary_specialization", + "enum": [ + "base", + "aigc", + "science" + ], + "examples": [ + "base", + "aigc", + "science" + ] + }, + "AigcModelType": { + "type": "string", + "description": "AIGC 模型子类型,可选值为 Checkpoint、LoRA、VAE(区分大小写);其他值返回 HTTP 400。仅适用于 AIGC 模型。", + "enum": [ + "Checkpoint", + "LoRA", + "VAE" + ], + "examples": [ + "Checkpoint", + "LoRA", + "VAE" + ] + }, + "AigcContentCategories": { + "type": "object", + "description": "AIGC 模型内容类别(三级联动,仅 model_category=aigc 时有效)", + "properties": { + "style": { + "type": "string", + "description": "设计风格", + "examples": [ + "设计风格-插画", + "设计风格-写实" + ] + }, + "function": { + "type": "string", + "description": "模型功能", + "examples": [ + "模型功能-文生图", + "模型功能-图生图" + ] + }, + "element": { + "type": "string", + "description": "内容元素", + "examples": [ + "内容元素-人物", + "内容元素-风景" + ] + } + } + }, + "SampleImage": { + "type": "object", + "additionalProperties": false, + "description": "AIGC 样例图片。请求和响应仅使用 url、prompt、negative_prompt;url 必填,prompt 和 negative_prompt 选填;不接受 text 字段。", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "HTTP(S) 图片 URL" + }, + "prompt": { + "type": "string", + "maxLength": 2000, + "description": "正向提示词" + }, + "negative_prompt": { + "type": "string", + "maxLength": 1000, + "description": "负向提示词" + } + }, + "required": [ + "url" + ] + }, + "ModelVersion": { + "type": "object", + "required": [ + "version_id", + "version_name", + "weight_files", + "sample_images", + "created_at" + ], + "properties": { + "version_id": { + "type": "integer", + "format": "int64" + }, + "version_name": { + "type": "string" + }, + "weight_files": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path_in_repo", + "size", + "sha" + ], + "properties": { + "path_in_repo": { + "type": "string" + }, + "size": { + "type": "integer", + "format": "int64" + }, + "sha": { + "type": "string" + } + } + } + }, + "sample_images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SampleImage" + } + }, + "trigger_words": { + "type": "array", + "items": { + "type": "string" + } + }, + "version_description": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "WeightFileReference": { + "type": "object", + "description": "AIGC 模型版本权重文件引用。文件需已上传至仓库,通过 path_in_repo 指定相对路径。", + "properties": { + "path_in_repo": { + "type": "string", + "description": "权重文件在仓库内的相对路径。支持的后缀(不区分大小写):.safetensors、.ckpt、.pt、.pth、.bin、.gguf、.ggml、.h5、.hdf5、.npz、.msgpack、.pdparams、.params、.onnx。每个文件均须符合,非权重后缀返回 400;英文 message 会指出无效字段,引导用户指定正确权重路径、先通过 Git 或网页上传权重,并列出全部支持格式。文件须已存在;后缀检查不代表已验证文件内容或推理兼容性。", + "examples": [ + "model.safetensors", + "checkpoints/model.ckpt", + "vae/diffusion_pytorch_model.bin" + ] + } + }, + "required": [ + "path_in_repo" + ] + }, + "SetModelCategoryRequest": { + "type": "object", + "description": "设置类型;目标为 AIGC(包括创建新 tag)时必须提供架构、version_name、非空 weight_files 和 sample_images。已有 tag 权重不可修改。目标为 Science 时 primary_subject_area、secondary_specialization 必填,cover_image 选填;base 只需 model_category。基础模型由架构子类型确定。", + "additionalProperties": false, + "properties": { + "model_category": { + "$ref": "#/components/schemas/ModelCategory" + }, + "aigc_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AigcModelType" + } + ], + "description": "【AIGC 必填】模型子类型,可选值为 Checkpoint、LoRA、VAE(区分大小写);其他值返回 HTTP 400。" + }, + "base_model_architecture_type": { + "type": "string", + "description": "【AIGC 必填】基础模型架构类型(一级选择)。可选值查询 GET /model/aigc/options", + "examples": [ + "Z_IMAGE_TURBO", + "FLUX_2" + ] + }, + "base_model_architecture_subtype": { + "type": "string", + "description": "【AIGC 必填】基础模型架构子类型(二级选择),需与 base_model_architecture_type 匹配。可选值查询 GET /model/aigc/options", + "examples": [ + "Z_IMAGE_TURBO", + "FLUX2_DEV" + ] + }, + "content_categories": { + "allOf": [ + { + "$ref": "#/components/schemas/AigcContentCategories" + } + ], + "description": "【AIGC 可选】内容类别三级联动" + }, + "version_name": { + "type": "string", + "description": "【AIGC 必填】版本名称,建议使用语义化版本,如 v1.0", + "maxLength": 20, + "examples": [ + "v1.0" + ] + }, + "trigger_words": { + "type": "array", + "description": "【AIGC 可选】触发词列表", + "items": { + "type": "string" + }, + "examples": [ + [ + "anime style" + ] + ] + }, + "sample_images": { + "type": "array", + "description": "AIGC tag 必填且非空;tag 样例图片。", + "items": { + "$ref": "#/components/schemas/SampleImage" + }, + "minItems": 1 + }, + "weight_files": { + "type": "array", + "description": "AIGC tag 必填且非空;仓库中已存在的权重文件路径。", + "items": { + "$ref": "#/components/schemas/WeightFileReference" + }, + "minItems": 1 + }, + "cover_image": { + "type": "string", + "description": "【Science 可选】模型封面图;可省略或传空字符串,非空时须为 HTTP(S) URL。", + "anyOf": [ + { + "format": "uri", + "pattern": "^https?://" + }, + { + "const": "" + } + ] + }, + "primary_subject_area": { + "allOf": [ + { + "type": "string" + } + ], + "description": "【科学 必填】一级学科分类,实际值以 GET /model/science/options 为准,例如 life-sciences" + }, + "secondary_specialization": { + "allOf": [ + { + "type": "string" + } + ], + "description": "【科学 必填】二级专业分类,应与 primary_subject_area 匹配;实际值以 GET /model/science/options 为准,例如 dna" + }, + "version_description": { + "type": "string", + "description": "AIGC tag 版本描述" + } + }, + "required": [ + "model_category" + ], + "allOf": [ + { + "if": { + "properties": { + "model_category": { + "const": "aigc" + } + }, + "required": [ + "model_category" + ] + }, + "then": { + "required": [ + "aigc_type", + "base_model_architecture_type", + "base_model_architecture_subtype", + "version_name", + "weight_files", + "sample_images" + ], + "properties": { + "model_category": {}, + "content_categories": {}, + "trigger_words": {}, + "version_description": {}, + "aigc_type": {}, + "base_model_architecture_type": {}, + "base_model_architecture_subtype": {}, + "version_name": {}, + "weight_files": {}, + "sample_images": {} + } + } + }, + { + "if": { + "properties": { + "model_category": { + "const": "science" + } + }, + "required": [ + "model_category" + ] + }, + "then": { + "required": [ + "primary_subject_area", + "secondary_specialization" + ], + "properties": { + "model_category": {}, + "cover_image": {}, + "primary_subject_area": {}, + "secondary_specialization": {} + } + } + } + ] + }, + "SetModelCategoryResponse": { + "type": "object", + "description": "设置/变更模型类型标准响应包裹格式", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/ModelCreated" + }, + "request_id": { + "type": "string", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "UpdateModelCategoryRequest": { + "type": "object", + "additionalProperties": false, + "description": "类型由服务器读取当前仓库确定,不接受 model_category(传入返回 400),类型转换使用 PUT /category;tag 字段用 version_id 定位;已有 tag 的权重不可修改,请 PUT /category 创建新 tag。省略不变,显式空值清空;必填约束仍需满足。", + "properties": { + "version_id": { + "type": "integer", + "format": "int64", + "minimum": 1, + "description": "仅适用于 AIGC:更新 trigger_words、version_description 或 sample_images 时必填,取自 GET 详情 versions[].version_id。Science 更新学科或 cover_image 不需要且不应传此字段;Science 详情不返回 versions。" + }, + "aigc_type": { + "anyOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/AigcModelType" + } + ], + "description": "【AIGC 字段;省略保持不变,不能清空】模型子类型,可选值为 Checkpoint、LoRA、VAE(区分大小写);其他值返回 HTTP 400。" + }, + { + "type": "null" + } + ] + }, + "base_model_architecture_type": { + "anyOf": [ + { + "type": "string", + "description": "【AIGC 字段;省略保持不变,不能清空】基础模型架构类型(一级选择)。可选值查询 GET /model/aigc/options", + "examples": [ + "Z_IMAGE_TURBO", + "FLUX_2" + ] + }, + { + "type": "null" + } + ] + }, + "base_model_architecture_subtype": { + "anyOf": [ + { + "type": "string", + "description": "【AIGC 字段;省略保持不变,不能清空】基础模型架构子类型(二级选择),需与 base_model_architecture_type 匹配。可选值查询 GET /model/aigc/options", + "examples": [ + "Z_IMAGE_TURBO", + "FLUX2_DEV" + ] + }, + { + "type": "null" + } + ] + }, + "content_categories": { + "anyOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/AigcContentCategories" + } + ], + "description": "【AIGC 可选】内容类别三级联动" + }, + { + "type": "null" + } + ] + }, + "trigger_words": { + "anyOf": [ + { + "type": "array", + "description": "【AIGC 可选】触发词列表", + "items": { + "type": "string" + }, + "examples": [ + [ + "anime style" + ] + ] + }, + { + "type": "null" + } + ] + }, + "sample_images": { + "type": "array", + "description": "可省略以保持原样;传入时替换指定 version_id 的全部样例图,必须非空。图片由现有媒体审核流程处理。", + "items": { + "$ref": "#/components/schemas/SampleImage" + }, + "minItems": 1 + }, + "version_description": { + "anyOf": [ + { + "type": "string", + "description": "AIGC tag 版本描述" + }, + { + "type": "null" + } + ] + }, + "primary_subject_area": { + "anyOf": [ + { + "allOf": [ + { + "type": "string" + } + ], + "description": "【科学 字段;省略保持不变,不能清空】一级学科分类,实际值以 GET /model/science/options 为准,例如 life-sciences" + }, + { + "type": "null" + } + ] + }, + "secondary_specialization": { + "anyOf": [ + { + "allOf": [ + { + "type": "string" + } + ], + "description": "【科学 字段;省略保持不变,不能清空】二级专业分类,应与 primary_subject_area 匹配;实际值以 GET /model/science/options 为准,例如 dna" + }, + { + "type": "null" + } + ] + }, + "cover_image": { + "anyOf": [ + { + "type": "string", + "description": "【仅科学 可选】模型封面图 URL", + "format": "uri" + }, + { + "type": "null" + } + ] + } + } + }, + "ModelOptionItem": { + "type": "object", + "description": "单个可选项,包含 type(自描述分类)、id 和 label;label 可用于各处直接展示,提交参数使用 id", + "properties": { + "type": { + "type": "string", + "description": "选项所属分类(自描述)", + "examples": [ + "aigc_type", + "specialization" + ] + }, + "id": { + "type": "string", + "description": "实际传入 API 的值", + "examples": [ + "LoRA" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称,例如选项列表、筛选项和标签。提交参数请使用 id,不要使用 label。", + "examples": [ + "LoRA" + ] + } + }, + "required": [ + "type", + "id", + "label" + ] + }, + "BaseArchitectureOption": { + "type": "object", + "description": "AIGC 基础模型架构(一级)及其可用子类型(二级)", + "properties": { + "type": { + "type": "string", + "description": "选项所属分类(自描述),固定为 \"base_architecture\"", + "examples": [ + "base_architecture" + ] + }, + "id": { + "type": "string", + "description": "架构类型标识(一级)", + "examples": [ + "Z_IMAGE_TURBO" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称,例如选项列表、筛选项和标签。提交参数请使用 id,不要使用 label。", + "examples": [ + "Z_IMAGE_TURBO" + ] + }, + "subtypes": { + "type": "array", + "description": "该架构下可用的子类型列表(二级)", + "items": { + "$ref": "#/components/schemas/ModelOptionItem" + } + } + }, + "required": [ + "type", + "id", + "label", + "subtypes" + ] + }, + "AigcModelOptions": { + "type": "object", + "description": "AIGC 专区配置项枚举", + "properties": { + "aigc_types": { + "type": "array", + "description": "AIGC 模型子类型选项", + "items": { + "$ref": "#/components/schemas/ModelOptionItem" + } + }, + "base_architectures": { + "type": "array", + "description": "AIGC 基础模型架构级联树(一级 type + 二级 subtype)", + "items": { + "$ref": "#/components/schemas/BaseArchitectureOption" + } + }, + "content_styles": { + "type": "array", + "description": "AIGC 内容类别 - 设计风格选项", + "items": { + "$ref": "#/components/schemas/ModelOptionItem" + } + }, + "content_functions": { + "type": "array", + "description": "AIGC 内容类别 - 模型功能选项", + "items": { + "$ref": "#/components/schemas/ModelOptionItem" + } + }, + "content_elements": { + "type": "array", + "description": "AIGC 内容类别 - 内容元素选项", + "items": { + "$ref": "#/components/schemas/ModelOptionItem" + } + } + }, + "required": [ + "aigc_types", + "base_architectures", + "content_styles", + "content_functions", + "content_elements" + ] + }, + "AigcModelOptionsResponse": { + "type": "object", + "description": "AIGC 专区配置项枚举标准响应包裹格式", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/AigcModelOptions" + }, + "request_id": { + "type": "string", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "ScienceOptionItem": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "specialization" + ] + }, + "id": { + "type": "string", + "description": "二级专业方向标识,用于 secondary_specialization。", + "examples": [ + "medicine" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称;提交参数使用 id。", + "examples": [ + "医学" + ] + } + }, + "required": [ + "type", + "id", + "label" + ] + }, + "ScienceSubjectOption": { + "type": "object", + "description": "科学模型一级学科及其下属二级专业方向", + "properties": { + "type": { + "type": "string", + "description": "选项所属分类(自描述),固定为 \"subject_area\"", + "examples": [ + "subject_area" + ] + }, + "id": { + "type": "string", + "description": "一级学科标识", + "examples": [ + "life-sciences" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称,例如选项列表、筛选项和标签。提交参数请使用 id,不要使用 label。", + "examples": [ + "生命科学" + ] + }, + "specializations": { + "type": "array", + "description": "该学科下的二级专业方向列表", + "items": { + "$ref": "#/components/schemas/ScienceOptionItem" + } + } + }, + "required": [ + "type", + "id", + "label", + "specializations" + ] + }, + "ScienceModelOptions": { + "type": "object", + "description": "科学模型专区配置项枚举", + "properties": { + "subject_areas": { + "type": "array", + "description": "学科分类树(一级学科 + 二级专业方向)", + "items": { + "$ref": "#/components/schemas/ScienceSubjectOption" + } + } + }, + "required": [ + "subject_areas" + ] + }, + "ScienceModelOptionsResponse": { + "type": "object", + "description": "科学模型专区配置项枚举标准响应包裹格式", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/ScienceModelOptions" + }, + "request_id": { + "type": "string", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "CreateDatasetRequest": { + "type": "object", + "description": "创建通用数据集仓库;成功后 dataset_category 为 base。科学专区字段通过 PUT /datasets/{owner}/{repo_name}/category 设置。", + "additionalProperties": false, + "properties": { + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "alice", + "my-org" + ] + }, + "repo_name": { + "type": "string", + "description": "仓库英文 ID,仅允许字母、数字、点、连字符和下划线,创建后不可修改", + "maxLength": 128, + "pattern": "^[a-zA-Z0-9._-]+$", + "examples": [ + "my-dataset", + "multilingual-dialogues" + ] + }, + "display_name": { + "type": "string", + "description": "数据集展示名称", + "maxLength": 128, + "examples": [ + "我的对话数据集" + ] + }, + "license": { + "type": "string", + "description": "许可证(SPDX 标识符),默认 apache-2.0。当传入 \"other\" 时可通过 license_name 和 license_link 自定义许可证", + "default": "apache-2.0", + "examples": [ + "apache-2.0", + "mit", + "other" + ] + }, + "license_name": { + "type": "string", + "description": "自定义许可证名称。license=\"other\" 时必填,其他许可证下忽略" + }, + "license_link": { + "type": "string", + "description": "【可选】自定义许可证链接,仅当 license=\"other\" 时生效。留空时仓库将自动在根目录创建 LICENSE.md 空白文件", + "format": "uri" + }, + "visibility": { + "type": "string", + "description": "仓库可见性,默认 public。protected 仅适用于 Studio,创建数据集时不接受该值", + "enum": [ + "public", + "private", + "gated" + ], + "default": "public" + }, + "gated_mode": { + "description": "创建时仅 visibility=\"gated\" 可传 gated_mode:auto 自动审批,manual 人工审批;省略或空字符串默认 auto。null、布尔值、数字及其他非法值返回 400。visibility 为 public/private(含省略后默认 public)时禁止传 gated_mode。请求仅接受文档列出的字段,未知字段返回 400。", + "type": "string", + "enum": [ + "auto", + "manual", + "" + ] + }, + "description": { + "type": "string", + "description": "数据集简介", + "maxLength": 2000, + "examples": [ + "用于多语言对话训练的数据集" + ] + } + }, + "required": [ + "owner", + "repo_name" + ] + }, + "DatasetCreated": { + "type": "object", + "description": "创建或更新后的数据集仓库标识", + "properties": { + "id": { + "type": "string", + "description": "数据集仓库唯一标识(格式:owner/repo_name)", + "examples": [ + "alice/my-dataset" + ] + }, + "repo_name": { + "type": "string", + "description": "仓库英文 ID", + "examples": [ + "my-dataset" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ], + "description": "数据集展示名称", + "examples": [ + "我的数据集" + ] + }, + "owner": { + "type": "string", + "description": "所有者(用户名或组织名)", + "examples": [ + "alice" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "数据集平台访问地址", + "examples": [ + "https://modelscope.cn/datasets/alice/my-dataset" + ] + } + }, + "required": [ + "id", + "repo_name", + "owner", + "url" + ] + }, + "DatasetWriteResponse": { + "type": "object", + "description": "创建或更新数据集的标准响应;所有成功和错误响应均有 request_id。", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/DatasetCreated" + }, + "request_id": { + "type": "string", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "McpServerList": { + "type": "object", + "description": "MCP服务列表与分页信息", + "properties": { + "mcp_server_list": { + "type": "array", + "description": "MCP服务列表", + "items": { + "$ref": "#/components/schemas/McpServerSummary" + } + }, + "total_count": { + "type": "integer", + "description": "搜索符合条件的mcp server总个数", + "examples": [ + 100 + ] + } + }, + "required": [ + "mcp_server_list", + "total_count" + ] + }, + "McpServerOperationalList": { + "type": "object", + "description": "用户托管MCP服务列表", + "properties": { + "mcp_server_list": { + "type": "array", + "description": "用户托管MCP服务列表", + "items": { + "$ref": "#/components/schemas/McpServerOperational" + } + }, + "total_count": { + "type": "integer", + "description": "服务链接总数", + "examples": [ + 5 + ] + } + }, + "required": [ + "mcp_server_list", + "total_count" + ] + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Studio ID (owner/repo_name)" + }, + "repo_name": { + "type": "string", + "description": "仓库名称" + }, + "display_name": { + "type": "string", + "description": "显示名称" + }, + "owner": { + "type": "string", + "description": "所有者" + }, + "description": { + "type": "string", + "description": "描述" + }, + "cover_image": { + "type": "string", + "description": "封面图 URL" + }, + "likes": { + "type": "integer", + "description": "喜欢数" + }, + "view_count": { + "type": "integer", + "description": "访问量" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "visibility": { + "$ref": "#/components/schemas/StudioVisibility" + }, + "private": { + "type": "boolean", + "description": "Deprecated: 请使用 visibility 字段", + "deprecated": true + }, + "last_modified": { + "type": "string", + "description": "最后修改时间" + }, + "sdk_type": { + "$ref": "#/components/schemas/StudioSDKType" + }, + "sdk_version": { + "$ref": "#/components/schemas/StudioSDKVersion" + }, + "hardware": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioHardware" + } + ], + "description": "期望的硬件配置(数据库中保存的值)" + }, + "base_image": { + "allOf": [ + { + "$ref": "#/components/schemas/StudioBaseImage" + } + ], + "description": "期望的基础镜像(Docker 类型不返回)" + }, + "license": { + "type": "string", + "description": "许可证" + }, + "host": { + "type": "string", + "description": "访问地址" + }, + "mcp_support": { + "type": "boolean", + "description": "是否支持 MCP" + }, + "runtime": { + "$ref": "#/components/schemas/StudioRuntime" + } + } + }, + "ListStudiosResponse": { + "type": "object", + "description": "获取创空间列表响应", + "properties": { + "studios": { + "type": "array", + "description": "创空间列表", + "items": { + "$ref": "#/components/schemas/data" + } + }, + "total_count": { + "type": "integer", + "description": "符合筛选条件的创空间总数", + "examples": [ + 120 + ] + }, + "page_number": { + "type": "integer", + "description": "当前页码", + "examples": [ + 1 + ] + }, + "page_size": { + "type": "integer", + "description": "每页大小", + "examples": [ + 10 + ] + } + }, + "required": [ + "studios", + "total_count", + "page_number", + "page_size" + ] + }, + "CollectionMutationResponse": { + "type": "object", + "description": "创建/更新 Collection 的标准响应", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "type": "object", + "description": "变更结果", + "properties": { + "slug": { + "type": "string", + "description": "Collection slug(重命名时可能变化)", + "examples": [ + "iSolver/best-nlp-models-6a3b2c" + ] + }, + "title": { + "type": "string", + "description": "Collection 标题", + "examples": [ + "最佳 NLP 模型合集" + ] + }, + "url": { + "type": "string", + "description": "Collection 页面 URL", + "examples": [ + "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + ] + } + }, + "required": [ + "slug", + "title", + "url" + ] + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "EmptyDataResponse": { + "type": "object", + "description": "删除/移除类操作的标准响应,结果以 success 字段为准,data 为空对象", + "properties": { + "success": { + "type": "boolean", + "description": "成功时始终为 true", + "examples": [ + true + ] + }, + "request_id": { + "type": "string", + "description": "请求 ID", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd3" + ] + }, + "data": { + "type": "object", + "description": "空对象" + } + }, + "required": [ + "success", + "request_id", + "data" + ] + }, + "ModelSettingsRequest": { + "type": "object", + "additionalProperties": false, + "description": "PATCH:省略不变,显式空值清空;visibility 决定访问模式,gated_mode 用于申请制仓库的审批方式;可同时传 visibility=gated 开启申请制。", + "properties": { + "task": { + "type": [ + "string", + "null" + ], + "description": "模型任务,取 GET /models/options 的 tasks[].id。省略保持不变;null 或空字符串清空。未知任务返回 HTTP 400。", + "examples": [ + "text-generation" + ] + }, + "display_name": { + "type": [ + "string", + "null" + ], + "description": "模型展示名称(中英文均可),用于 UI 展示", + "maxLength": 128, + "examples": [ + "我的大语言模型" + ] + }, + "license": { + "type": [ + "string", + "null" + ], + "description": "许可证标识;省略保持不变,显式 null 或空串清空。", + "examples": [ + "apache-2.0", + "mit", + "other" + ] + }, + "visibility": { + "type": [ + "string", + "null" + ], + "enum": [ + "public", + "private", + "gated", + "", + null + ], + "description": "仓库访问模式;public/private 关闭申请制,gated 开启申请制;省略保持不变。" + }, + "gated_mode": { + "type": "string", + "enum": [ + "auto", + "manual", + "" + ], + "description": "gated_mode 取 auto 自动审批、manual 人工审批;显式 visibility=\"gated\" 时,gated_mode 省略或空字符串默认 auto;两字段均省略保持原设置;无论 visibility 取值,显式 null、布尔值、数字及其他非法值均返回 400;visibility 为 public/private 时禁止传 gated_mode(含空串和 null),否则返回 400;PATCH 省略 visibility 时,仅当前仓库已是申请制才可用 gated_mode 修改审批方式。关闭申请制请将 visibility 改为 public 或 private。" + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "模型简介", + "maxLength": 2000, + "examples": [ + "基于 Qwen 微调的对话模型" + ] + } + } + }, + "DatasetCategoryRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "dataset_category" + ], + "properties": { + "dataset_category": { + "type": "string", + "enum": [ + "base", + "science" + ] + }, + "primary_subject_area": { + "type": [ + "string", + "null" + ] + }, + "secondary_specialization": { + "type": [ + "string", + "null" + ] + }, + "cover_image": { + "type": [ + "string", + "null" + ], + "description": "科学数据集封面;显式空值清空。" + } + } + }, + "UpdateDatasetCategoryRequest": { + "type": "object", + "additionalProperties": false, + "description": "类型由服务器读取当前仓库确定,不接受 dataset_category(传入返回 400),类型转换使用 PUT /category。", + "properties": { + "primary_subject_area": { + "type": [ + "string", + "null" + ] + }, + "secondary_specialization": { + "type": [ + "string", + "null" + ] + }, + "cover_image": { + "type": [ + "string", + "null" + ], + "description": "科学数据集封面;显式空值清空。" + } + } + }, + "DatasetSettingsRequest": { + "type": "object", + "additionalProperties": false, + "description": "PATCH:省略不变,显式空值清空;visibility 决定访问模式,gated_mode 用于申请制仓库的审批方式;可同时传 visibility=gated 开启申请制。", + "properties": { + "display_name": { + "type": [ + "string", + "null" + ], + "description": "数据集展示名称(中英文均可),用于 UI 展示", + "maxLength": 128, + "examples": [ + "我的数据集" + ] + }, + "license": { + "type": [ + "string", + "null" + ], + "description": "许可证标识;省略保持不变,显式 null 或空串清空。", + "examples": [ + "apache-2.0", + "mit", + "other" + ] + }, + "visibility": { + "type": [ + "string", + "null" + ], + "enum": [ + "public", + "private", + "gated", + "", + null + ], + "description": "仓库访问模式;public/private 关闭申请制,gated 开启申请制;省略保持不变。" + }, + "gated_mode": { + "type": "string", + "enum": [ + "auto", + "manual", + "" + ], + "description": "gated_mode 取 auto 自动审批、manual 人工审批;显式 visibility=\"gated\" 时,gated_mode 省略或空字符串默认 auto;两字段均省略保持原设置;无论 visibility 取值,显式 null、布尔值、数字及其他非法值均返回 400;visibility 为 public/private 时禁止传 gated_mode(含空串和 null),否则返回 400;PATCH 省略 visibility 时,仅当前仓库已是申请制才可用 gated_mode 修改审批方式。关闭申请制请将 visibility 改为 public 或 private。" + }, + "description": { + "type": [ + "string", + "null" + ], + "description": "数据集简介", + "maxLength": 2000, + "examples": [ + "用于文本分类的数据集" + ] + } + } + }, + "DeleteFilesRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "file_paths" + ], + "properties": { + "file_paths": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "type": "string" + }, + "description": "仓库相对文件路径;不允许目录、重复或越界路径。" + }, + "revision": { + "type": "string", + "default": "master", + "description": "目标分支,不接受 tag。" + }, + "commit_message": { + "type": "string", + "default": "Delete files" + } + } + }, + "DeleteFilesResponse": { + "type": "object", + "required": [ + "success", + "request_id", + "data" + ], + "properties": { + "success": { + "type": "boolean" + }, + "request_id": { + "type": "string" + }, + "data": { + "type": "object", + "required": [ + "deleted_files", + "failed_files", + "total_files", + "commit_sha" + ], + "properties": { + "deleted_files": { + "type": "array", + "items": { + "type": "string" + } + }, + "failed_files": { + "type": "array", + "maxItems": 0, + "items": { + "type": "string" + }, + "description": "成功时为空;任何失败整个请求失败,不产生部分删除。" + }, + "total_files": { + "type": "integer" + }, + "commit_sha": { + "type": "string" + } + } + } + } + }, + "DatasetScienceOptionItem": { + "type": "object", + "description": "单个可选项,包含 type(自描述分类)、id 和 label;label 可用于各处直接展示,提交参数使用 id", + "properties": { + "type": { + "type": "string", + "description": "选项所属分类(自描述)", + "examples": [ + "specialization" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称,例如选项列表、筛选项和标签。提交参数请使用 id,不要使用 label。", + "examples": [ + "医学" + ] + }, + "id": { + "type": "string", + "description": "实际传入 API 的值", + "examples": [ + "medicine" + ] + } + }, + "required": [ + "type", + "id", + "label" + ] + }, + "DatasetScienceSubjectOption": { + "type": "object", + "description": "科学数据集一级学科及其下属二级专业方向", + "properties": { + "type": { + "type": "string", + "description": "选项所属分类(自描述),固定为 \"subject_area\"", + "examples": [ + "subject_area" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称,例如选项列表、筛选项和标签。提交参数请使用 id,不要使用 label。", + "examples": [ + "生命科学" + ] + }, + "specializations": { + "type": "array", + "description": "该学科下的二级专业方向列表", + "items": { + "$ref": "#/components/schemas/DatasetScienceOptionItem" + } + }, + "id": { + "type": "string", + "description": "一级学科标识", + "examples": [ + "life-sciences" + ] + } + }, + "required": [ + "type", + "id", + "label", + "specializations" + ] + }, + "ScienceDatasetOptions": { + "type": "object", + "description": "科学数据集专区配置项枚举", + "properties": { + "subject_areas": { + "type": "array", + "description": "学科分类树(一级学科 + 二级专业方向)", + "items": { + "$ref": "#/components/schemas/DatasetScienceSubjectOption" + } + } + }, + "required": [ + "subject_areas" + ] + }, + "ScienceDatasetOptionsResponse": { + "type": "object", + "description": "科学数据集专区配置项枚举标准响应包裹格式", + "properties": { + "success": { + "type": "boolean", + "examples": [ + true + ] + }, + "data": { + "$ref": "#/components/schemas/ScienceDatasetOptions" + }, + "request_id": { + "type": "string", + "examples": [ + "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + ] + } + }, + "required": [ + "success", + "data", + "request_id" + ] + }, + "ModelOptionsResponse": { + "type": "object", + "required": [ + "success", + "data", + "request_id" + ], + "properties": { + "success": { + "type": "boolean" + }, + "request_id": { + "type": "string" + }, + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "tasks", + "license" + ], + "properties": { + "tasks": { + "type": "array", + "description": "模型任务选项。id 是任务标识字符串,可用于创建模型、更新模型设置的 task,以及 GET /models 的 filter.task。", + "items": { + "type": "object", + "required": [ + "type", + "id", + "label" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "task" + ] + }, + "id": { + "type": "string", + "description": "接口接受的任务标识字符串。", + "examples": [ + "text-generation" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的名称,例如选项列表、筛选项和标签。传参使用 id,不要使用 label。", + "examples": [ + "文本生成" + ] + } + } + } + }, + "license": { + "type": "array", + "description": "模型许可证选项。id 可直接作为创建或更新模型的 license 入参;other 仍需在创建时提供 license_name。", + "items": { + "type": "object", + "required": [ + "type", + "id", + "label" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "license" + ] + }, + "id": { + "type": "string", + "description": "接口接受的许可证标识字符串。", + "examples": [ + "apache-2.0", + "mit", + "other" + ] + }, + "label": { + "type": "string", + "description": "可用于各处直接展示的许可证名称。传参使用 id,不要使用 label。", + "examples": [ + "Apache License 2.0", + "MIT License" + ] + } + } + } + } + } + } + } + } + }, + "responses": { + "bad-request": { + "description": "请求参数错误", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "input-parameter-error": { + "$ref": "#/components/examples/input-parameter-error" + } + } + } + } + }, + "unauthorized": { + "description": "认证失败(token 无效、过期或未提供)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "invalid-authentication": { + "$ref": "#/components/examples/invalid-authentication" + } + } + } + } + }, + "forbidden": { + "description": "权限不足(token 有效但无权执行该操作,或配额超限)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "operation-not-allowed": { + "$ref": "#/components/examples/operation-not-allowed" + }, + "quota-limit-exceed": { + "$ref": "#/components/examples/quota-limit-exceed" + } + } + } + } + }, + "not-found": { + "description": "资源未找到", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "resource-not-found": { + "$ref": "#/components/examples/resource-not-found" + } + } + } + } + }, + "internal-server-error": { + "description": "内部服务错误", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "internal-server-error": { + "$ref": "#/components/examples/internal-server-error" + } + } + } + } + }, + "service-unavailable": { + "description": "服务不可用", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "service-unavailable": { + "$ref": "#/components/examples/service-unavailable" + } + } + } + } + }, + "get-current-user-success": { + "description": "成功获取当前用户信息", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "examples": { + "user-all-response": { + "$ref": "#/components/examples/user-all-response" + } + } + } + } + }, + "list-models-success": { + "description": "成功获取模型列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelListResponse" + }, + "examples": { + "list-models-response": { + "$ref": "#/components/examples/list-models-response" + } + } + } + } + }, + "get-model-success": { + "description": "成功获取模型详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelDetailResponse" + }, + "examples": { + "get-model-response": { + "$ref": "#/components/examples/get-model-response" + } + } + } + } + }, + "list-datasets-success": { + "description": "成功获取数据集列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetListResponse" + }, + "examples": { + "list-datasets-response": { + "$ref": "#/components/examples/list-datasets-response" + } + } + } + } + }, + "get-dataset-success": { + "description": "成功获取数据集详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatasetDetailResponse" + }, + "examples": { + "get-dataset-response": { + "$ref": "#/components/examples/get-dataset-response" + } + } + } + } + }, + "list-mcp-servers-success": { + "description": "成功获取MCP服务列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/McpServerListResponse" + }, + "examples": { + "list-mcp-servers-response": { + "$ref": "#/components/examples/list-mcp-servers-response" + } + } + } + } + }, + "list-operational-mcp-servers-success": { + "description": "成功获取用户托管MCP服务列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/McpServerOperationalListResponse" + }, + "examples": { + "list-operational-mcp-servers-response": { + "$ref": "#/components/examples/list-operational-mcp-servers-response" + } + } + } + } + }, + "get-mcp-server-success": { + "description": "成功获取MCP服务详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/McpServerDetailResponse" + }, + "examples": { + "get-mcp-server-response": { + "$ref": "#/components/examples/get-mcp-server-response" + } + } + } + } + }, + "deploy-mcp-server-success": { + "description": "成功部署MCP服务", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/McpDeployResponse" + }, + "examples": { + "deploy-mcp-server-response": { + "$ref": "#/components/examples/deploy-mcp-server-response" + } + } + } + } + }, + "undeploy-mcp-server-success": { + "description": "成功解除MCP服务部署", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/McpUndeployResponse" + }, + "examples": { + "undeploy-mcp-server-response": { + "$ref": "#/components/examples/undeploy-mcp-server-response" + } + } + } + } + }, + "create-studio-success": { + "description": "成功创建 Studio", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateStudioResponse" + }, + "examples": { + "create-studio-response": { + "$ref": "#/components/examples/create-studio-response" + } + } + } + } + }, + "get-studio-success": { + "description": "成功获取 Studio 详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetStudioResponse" + }, + "examples": { + "get-studio-response": { + "$ref": "#/components/examples/get-studio-response" + } + } + } + } + }, + "update-studio-settings-success": { + "description": "成功更新 Studio 设置", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetStudioResponse" + }, + "examples": { + "update-studio-settings-response": { + "$ref": "#/components/examples/get-studio-response" + } + } + } + } + }, + "studio-operation-success": { + "description": "Studio 操作成功", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "studio-runtime-success": { + "description": "Studio 运行时操作成功", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioRuntimeResponse" + }, + "examples": { + "studio-runtime-response": { + "$ref": "#/components/examples/studio-runtime-response" + } + } + } + } + }, + "get-studio-logs-success": { + "description": "成功获取 Studio 日志", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StudioLogsResponse" + }, + "examples": { + "get-studio-logs-response": { + "$ref": "#/components/examples/get-studio-logs-response" + } + } + } + } + }, + "list-hardware-success": { + "description": "成功获取可用硬件配置列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListHardwareResponse" + } + } + } + }, + "list-sdk-versions-success": { + "description": "成功获取可用 SDK 版本列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListSDKVersionsResponse" + }, + "examples": { + "list-sdk-versions-response": { + "$ref": "#/components/examples/list-sdk-versions-response" + } + } + } + } + }, + "list-base-images-success": { + "description": "成功获取可用基础镜像列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListBaseImagesResponse" + } + } + } + }, + "list-studio-variables-success": { + "description": "成功获取明文变量列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListStudioVariablesResponse" + } + } + } + }, + "list-skills-success": { + "description": "成功获取技能列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillListResponse" + }, + "examples": { + "list-skills-response": { + "$ref": "#/components/examples/list-skills-response" + } + } + } + } + }, + "get-skill-success": { + "description": "成功获取技能详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SkillDetailResponse" + }, + "examples": { + "get-skill-response": { + "$ref": "#/components/examples/get-skill-response" + } + } + } + } + }, + "create-skill-success": { + "description": "成功创建技能", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSkillResponse" + }, + "examples": { + "create-skill-response": { + "$ref": "#/components/examples/create-skill-response" + } + } + } + } + }, + "update-skill-settings-success": { + "description": "成功更新技能设置", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + }, + "examples": { + "update-skill-settings-response": { + "$ref": "#/components/examples/update-skill-settings-response" + } + } + } + } + }, + "skill-bad-request": { + "description": "请求参数错误", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "skill-input-parameter-error": { + "$ref": "#/components/examples/skill-input-parameter-error" + } + } + } + } + }, + "skill-unauthorized": { + "description": "未通过认证", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "skill-invalid-authentication": { + "$ref": "#/components/examples/skill-invalid-authentication" + } + } + } + } + }, + "skill-not-found": { + "description": "技能未找到", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "skill-not-found": { + "$ref": "#/components/examples/skill-not-found" + } + } + } + } + }, + "skill-conflict": { + "description": "资源冲突(如 Skill 已存在)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "skill-duplicate-entity": { + "$ref": "#/components/examples/skill-duplicate-entity" + } + } + } + } + }, + "skill-internal-server-error": { + "description": "内部服务错误", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "skill-internal-server-error": { + "$ref": "#/components/examples/skill-internal-server-error" + } + } + } + } + }, + "skill-service-unavailable": { + "description": "服务不可用", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "skill-service-unavailable": { + "$ref": "#/components/examples/skill-service-unavailable" + } + } + } + } + }, + "list-agents-success": { + "description": "成功获取 Agent 列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentListResponse" + }, + "examples": { + "list-agents-response": { + "$ref": "#/components/examples/list-agents-response" + } + } + } + } + }, + "create-agent-success": { + "description": "成功创建 Agent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAgentResponse" + }, + "examples": { + "create-agent-response": { + "$ref": "#/components/examples/create-agent-response" + } + } + } + } + }, + "get-agent-success": { + "description": "成功获取 Agent 详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAgentResponse" + }, + "examples": { + "get-agent-response": { + "$ref": "#/components/examples/get-agent-response" + } + } + } + } + }, + "agent-bad-request": { + "description": "请求参数错误", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-input-parameter-error": { + "$ref": "#/components/examples/agent-input-parameter-error" + } + } + } + } + }, + "agent-unauthorized": { + "description": "未通过认证", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-invalid-authentication": { + "$ref": "#/components/examples/agent-invalid-authentication" + } + } + } + } + }, + "agent-forbidden": { + "description": "无权访问该 Agent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-operation-not-allowed": { + "$ref": "#/components/examples/agent-operation-not-allowed" + } + } + } + } + }, + "agent-quota-limit-exceeded": { + "description": "分页查询总量(page_number × page_size)超出上限", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-quota-limit-exceed": { + "$ref": "#/components/examples/agent-quota-limit-exceed" + } + } + } + } + }, + "agent-not-found": { + "description": "Agent 未找到", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-not-found": { + "$ref": "#/components/examples/agent-not-found" + } + } + } + } + }, + "agent-list-failed": { + "description": "Agent 列表查询失败", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-list-failed": { + "$ref": "#/components/examples/agent-list-failed" + } + } + } + } + }, + "agent-internal-server-error": { + "description": "内部服务错误", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-internal-server-error": { + "$ref": "#/components/examples/agent-internal-server-error" + } + } + } + } + }, + "upload-file-success": { + "description": "成功上传文件", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadFileResponse" + }, + "examples": { + "upload-file-response": { + "$ref": "#/components/examples/upload-file-response" + } + } + } + } + }, + "create-agent-identity-success": { + "description": "成功创建 Agent 身份", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentIdentityResponse" + }, + "examples": { + "create-agent-identity-response": { + "$ref": "#/components/examples/create-agent-identity-response" + } + } + } + } + }, + "get-agent-identity-success": { + "description": "成功获取 Agent 身份详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentIdentityResponse" + }, + "examples": { + "get-agent-identity-response": { + "$ref": "#/components/examples/get-agent-identity-response" + } + } + } + } + }, + "update-agent-identity-success": { + "description": "成功更新 Agent 身份设置", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentIdentityResponse" + }, + "examples": { + "update-agent-identity-response": { + "$ref": "#/components/examples/update-agent-identity-response" + } + } + } + } + }, + "list-agent-identities-success": { + "description": "成功获取 Agent 身份列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListAgentIdentitiesResponse" + }, + "examples": { + "list-agent-identities-response": { + "$ref": "#/components/examples/list-agent-identities-response" + } + } + } + } + }, + "reset-key-pair-success": { + "description": "成功重置 Agent 密钥对", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetKeyPairResponse" + }, + "examples": { + "reset-key-pair-response": { + "$ref": "#/components/examples/reset-key-pair-response" + } + } + } + } + }, + "pause-agent-success": { + "description": "成功启用/停用 Agent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PauseAgentResponse" + }, + "examples": { + "pause-agent-response": { + "$ref": "#/components/examples/pause-agent-response" + } + } + } + } + }, + "delete-agent-identity-success": { + "description": "成功删除 Agent 身份", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteAgentIdentityResponse" + }, + "examples": { + "delete-agent-identity-response": { + "$ref": "#/components/examples/delete-agent-identity-response" + } + } + } + } + }, + "issue-token-success": { + "description": "成功签发 Token", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenSignResponse" + }, + "examples": { + "issue-token-response": { + "$ref": "#/components/examples/issue-token-response" + } + } + } + } + }, + "list-token-records-success": { + "description": "成功获取 Token 签发记录列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListTokenRecordsResponse" + }, + "examples": { + "list-token-records-response": { + "$ref": "#/components/examples/list-token-records-response" + } + } + } + } + }, + "agentid-configuration-success": { + "description": "成功获取 Agent IDP OIDC 配置", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentIdConfiguration" + }, + "examples": { + "agentid-configuration-response": { + "$ref": "#/components/examples/agentid-configuration-response" + } + } + } + } + }, + "agentid-jwks-success": { + "description": "成功获取 JWKS", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JWKS" + }, + "examples": { + "agentid-jwks-response": { + "$ref": "#/components/examples/agentid-jwks-response" + } + } + } + } + }, + "get-collection-success": { + "description": "成功获取合集详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionResponse" + }, + "examples": { + "get-collection-response": { + "$ref": "#/components/examples/get-collection-response" + } + } + } + } }, - "skill-bad-request": { - "description": "请求参数错误", + "create-collection-success": { + "description": "成功创建合集", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/schemas/CollectionMutationResponse" }, "examples": { - "skill-input-parameter-error": { - "$ref": "#/components/examples/skill-input-parameter-error" + "create-collection-response": { + "$ref": "#/components/examples/create-collection-response" } } } } }, - "skill-unauthorized": { - "description": "未通过认证", + "update-collection-success": { + "description": "成功更新合集", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/schemas/CollectionMutationResponse" }, "examples": { - "skill-invalid-authentication": { - "$ref": "#/components/examples/skill-invalid-authentication" + "update-collection-response": { + "$ref": "#/components/examples/update-collection-response" } } } } }, - "skill-not-found": { - "description": "技能未找到", + "delete-collection-success": { + "description": "成功删除合集", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/schemas/EmptyDataResponse" }, "examples": { - "skill-not-found": { - "$ref": "#/components/examples/skill-not-found" + "delete-collection-response": { + "$ref": "#/components/examples/delete-collection-response" } } } } }, - "skill-conflict": { - "description": "资源冲突(如 Skill 已存在)", + "list-collections-success": { + "description": "成功获取合集列表", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/schemas/CollectionListResponse" }, "examples": { - "skill-duplicate-entity": { - "$ref": "#/components/examples/skill-duplicate-entity" + "list-collections-response": { + "$ref": "#/components/examples/list-collections-response" } } } } }, - "skill-internal-server-error": { - "description": "内部服务错误", + "list-collection-items-success": { + "description": "成功获取合集条目列表", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/schemas/CollectionItemListResponse" }, "examples": { - "skill-internal-server-error": { - "$ref": "#/components/examples/skill-internal-server-error" + "list-collection-items-response": { + "$ref": "#/components/examples/list-collection-items-response" } } } } }, - "skill-service-unavailable": { - "description": "服务不可用", + "add-collection-items-success": { + "description": "成功添加合集条目(部分失败仍返回 success=true,见 failed_items)", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorResponse" + "$ref": "#/components/schemas/AddCollectionItemsResponse" }, "examples": { - "skill-service-unavailable": { - "$ref": "#/components/examples/skill-service-unavailable" + "add-collection-items-response": { + "$ref": "#/components/examples/add-collection-items-response" } } } } }, - "upload-file-success": { - "description": "成功上传文件", + "update-collection-item-success": { + "description": "成功更新单个合集条目", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UploadFileResponse" + "$ref": "#/components/schemas/UpdateCollectionItemResponse" }, "examples": { - "upload-file-response": { - "$ref": "#/components/examples/upload-file-response" + "update-collection-item-response": { + "$ref": "#/components/examples/update-collection-item-response" } } } } }, - "create-agent-identity-success": { - "description": "成功创建 Agent 身份", + "remove-collection-item-success": { + "description": "成功移除合集条目", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentIdentityResponse" + "$ref": "#/components/schemas/EmptyDataResponse" }, "examples": { - "create-agent-identity-response": { - "$ref": "#/components/examples/create-agent-identity-response" + "remove-collection-item-response": { + "$ref": "#/components/examples/remove-collection-item-response" } } } } }, - "get-agent-identity-success": { - "description": "成功获取 Agent 身份详情", + "update-collection-items-success": { + "description": "成功批量更新合集条目(部分失败仍返回 success=true,见 failed_items)", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentIdentityResponse" + "$ref": "#/components/schemas/UpdateCollectionItemsResponse" }, "examples": { - "get-agent-identity-response": { - "$ref": "#/components/examples/get-agent-identity-response" + "update-collection-items-response": { + "$ref": "#/components/examples/update-collection-items-response" } } } } }, - "update-agent-identity-success": { - "description": "成功更新 Agent 身份设置", + "create-gallery-success": { + "description": "成功创建 Gallery", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentIdentityResponse" + "$ref": "#/components/schemas/CreateGalleryResponse" + }, + "examples": { + "create-gallery-response": { + "$ref": "#/components/examples/create-gallery-response" + } + } + } + } + }, + "create-agent-playground-instance-success": { + "description": "成功受理创建请求(异步创建,`status` 为 `starting`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "create-agent-playground-instance-response": { + "$ref": "#/components/examples/create-agent-playground-instance-response" + } + } + } + } + }, + "create-agent-playground-instance-from-agent-hub-success": { + "description": "成功受理创建请求(`source` 为 `agent_hub`,`status` 为 `starting`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "create-agent-playground-instance-from-agent-hub-response": { + "$ref": "#/components/examples/create-agent-playground-instance-from-agent-hub-response" + } + } + } + } + }, + "list-agent-playground-instances-success": { + "description": "成功获取实例列表", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceListResponse" + }, + "examples": { + "list-agent-playground-instances-response": { + "$ref": "#/components/examples/list-agent-playground-instances-response" + } + } + } + } + }, + "get-agent-playground-instance-success": { + "description": "成功获取实例详情", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "get-agent-playground-instance-response": { + "$ref": "#/components/examples/get-agent-playground-instance-response" + } + } + } + } + }, + "update-agent-playground-instance-success": { + "description": "成功更新实例元信息", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "update-agent-playground-instance-response": { + "$ref": "#/components/examples/update-agent-playground-instance-response" + } + } + } + } + }, + "delete-agent-playground-instance-success": { + "description": "成功受理删除(幂等)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteAgentPlaygroundInstanceResponse" + }, + "examples": { + "delete-agent-playground-instance-response": { + "$ref": "#/components/examples/delete-agent-playground-instance-response" + } + } + } + } + }, + "pause-agent-playground-instance-success": { + "description": "成功受理暂停", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "pause-agent-playground-instance-response": { + "$ref": "#/components/examples/pause-agent-playground-instance-response" + } + } + } + } + }, + "resume-agent-playground-instance-success": { + "description": "成功受理恢复", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "resume-agent-playground-instance-response": { + "$ref": "#/components/examples/resume-agent-playground-instance-response" + } + } + } + } + }, + "restart-agent-playground-instance-success": { + "description": "成功受理重启(异步操作,`status` 为 `starting`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "restart-agent-playground-instance-response": { + "$ref": "#/components/examples/restart-agent-playground-instance-response" + } + } + } + } + }, + "get-agent-playground-instance-agents-success": { + "description": "成功获取实例内 Agent 信息", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundAgentListResponse" + }, + "examples": { + "get-agent-playground-instance-agents-response": { + "$ref": "#/components/examples/get-agent-playground-instance-agents-response" + } + } + } + } + }, + "setup-agent-playground-instance-success": { + "description": "成功下发配置,返回最新的实例对象(下发不改变实例 `status`)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "setup-agent-playground-instance-response": { + "$ref": "#/components/examples/setup-agent-playground-instance-response" + } + } + } + } + }, + "import-to-agent-playground-instance-success": { + "description": "成功受理导入(`status` 为 `starting`,结果经一次性通知返回)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundInstanceResponse" + }, + "examples": { + "import-to-agent-playground-instance-response": { + "$ref": "#/components/examples/import-to-agent-playground-instance-response" + } + } + } + } + }, + "get-agent-playground-instance-notifications-success": { + "description": "成功获取并消费未读通知", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentPlaygroundNotificationListResponse" + }, + "examples": { + "get-agent-playground-instance-notifications-response": { + "$ref": "#/components/examples/get-agent-playground-instance-notifications-response" + } + } + } + } + }, + "agent-playground-bad-request": { + "description": "参数非法、引用不存在、实例状态不满足前置条件、实例数超配额或内容安全不通过", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "agent-playground-input-parameter-error": { + "$ref": "#/components/examples/agent-playground-input-parameter-error" + } + } + } + } + }, + "agent-playground-unauthorized": { + "description": "未认证或 Token 无效", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "update-agent-identity-response": { - "$ref": "#/components/examples/update-agent-identity-response" + "agent-playground-invalid-authentication": { + "$ref": "#/components/examples/agent-playground-invalid-authentication" } } } } }, - "list-agent-identities-success": { - "description": "成功获取 Agent 身份列表", + "agent-playground-forbidden": { + "description": "非资源 owner、跨账号操作或 Token 权限档不足", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListAgentIdentitiesResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "list-agent-identities-response": { - "$ref": "#/components/examples/list-agent-identities-response" + "agent-playground-operation-not-allowed": { + "$ref": "#/components/examples/agent-playground-operation-not-allowed" } } } } }, - "reset-key-pair-success": { - "description": "成功重置 Agent 密钥对", + "agent-playground-not-found": { + "description": "实例或 Agent Hub agent 不存在", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ResetKeyPairResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "reset-key-pair-response": { - "$ref": "#/components/examples/reset-key-pair-response" + "agent-playground-resource-not-found": { + "$ref": "#/components/examples/agent-playground-resource-not-found" } } } } }, - "pause-agent-success": { - "description": "成功启用/停用 Agent", + "agent-playground-conflict": { + "description": "实例名重复;实例内同名 MCP 或 Skill 已存在;并发写冲突(可直接重试)", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PauseAgentResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "pause-agent-response": { - "$ref": "#/components/examples/pause-agent-response" + "agent-playground-duplicate-entity": { + "$ref": "#/components/examples/agent-playground-duplicate-entity" } } } } }, - "delete-agent-identity-success": { - "description": "成功删除 Agent 身份", + "agent-playground-internal-server-error": { + "description": "服务端内部错误,排查请携带 `request_id`", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DeleteAgentIdentityResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "delete-agent-identity-response": { - "$ref": "#/components/examples/delete-agent-identity-response" + "agent-playground-internal-server-error": { + "$ref": "#/components/examples/agent-playground-internal-server-error" } } } } }, - "issue-token-success": { - "description": "成功签发 Token", + "agent-playground-bad-gateway": { + "description": "实例内服务未就绪或实例内操作失败,可稍后重试", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TokenSignResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "issue-token-response": { - "$ref": "#/components/examples/issue-token-response" + "agent-playground-service-unavailable": { + "$ref": "#/components/examples/agent-playground-service-unavailable" } } } } }, - "list-token-records-success": { - "description": "成功获取 Token 签发记录列表", + "agent-playground-service-unavailable": { + "description": "实例内服务未就绪或实例内操作失败,可稍后重试", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListTokenRecordsResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "list-token-records-response": { - "$ref": "#/components/examples/list-token-records-response" + "agent-playground-service-unavailable": { + "$ref": "#/components/examples/agent-playground-service-unavailable" } } } } }, - "agentid-configuration-success": { - "description": "成功获取 Agent IDP OIDC 配置", + "agent-playground-request-timeout": { + "description": "实例内操作超时;操作可能仍在实例内继续执行", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentIdConfiguration" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "agentid-configuration-response": { - "$ref": "#/components/examples/agentid-configuration-response" + "agent-playground-request-timeout": { + "$ref": "#/components/examples/agent-playground-request-timeout" } } } } }, - "agentid-jwks-success": { - "description": "成功获取 JWKS", + "create-model-success": { + "description": "模型创建成功", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JWKS" + "$ref": "#/components/schemas/CreateModelResponse" }, "examples": { - "agentid-jwks-response": { - "$ref": "#/components/examples/agentid-jwks-response" + "create-model-response": { + "$ref": "#/components/examples/model-repo-info-response" } } } } }, - "get-collection-success": { - "description": "成功获取合集详情", + "conflict": { + "description": "资源冲突", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CollectionResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "get-collection-response": { - "$ref": "#/components/examples/get-collection-response" + "duplicate-entity": { + "$ref": "#/components/examples/duplicate-entity" } } } } }, - "create-collection-success": { - "description": "成功创建合集", + "set-model-category-success": { + "description": "模型类型设置/变更成功", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CollectionMutationResponse" + "$ref": "#/components/schemas/SetModelCategoryResponse" }, "examples": { - "create-collection-response": { - "$ref": "#/components/examples/create-collection-response" + "set-model-category-response": { + "$ref": "#/components/examples/model-repo-info-response" } } } } }, - "update-collection-success": { - "description": "成功更新合集", + "aigc-model-options-success": { + "description": "成功返回 AIGC 模型配置项枚举", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CollectionMutationResponse" + "$ref": "#/components/schemas/AigcModelOptionsResponse" }, "examples": { - "update-collection-response": { - "$ref": "#/components/examples/update-collection-response" + "aigc-model-options-response": { + "$ref": "#/components/examples/aigc-model-options-response" } } } } }, - "delete-collection-success": { - "description": "成功删除合集", + "too-many-requests": { + "description": "请求过于频繁", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/EmptyDataResponse" + "$ref": "#/components/schemas/ErrorResponse" }, "examples": { - "delete-collection-response": { - "$ref": "#/components/examples/delete-collection-response" + "rate-limit-exceed": { + "$ref": "#/components/examples/rate-limit-exceed" } } } } }, - "list-collections-success": { - "description": "成功获取合集列表", + "science-model-options-success": { + "description": "成功返回科学模型配置项枚举", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CollectionListResponse" + "$ref": "#/components/schemas/ScienceModelOptionsResponse" }, "examples": { - "list-collections-response": { - "$ref": "#/components/examples/list-collections-response" + "science-model-options-response": { + "$ref": "#/components/examples/science-model-options-response" } } } } }, - "list-collection-items-success": { - "description": "成功获取合集条目列表", + "list-studios-success": { + "description": "成功获取创空间列表", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CollectionItemListResponse" - }, - "examples": { - "list-collection-items-response": { - "$ref": "#/components/examples/list-collection-items-response" - } + "$ref": "#/components/schemas/ListStudiosResponse" } } } }, - "add-collection-items-success": { - "description": "成功添加合集条目(部分失败仍返回 success=true,见 failed_items)", + "get-balance-success": { + "description": "余额查询成功", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AddCollectionItemsResponse" + "$ref": "#/components/schemas/GetBalanceResponse" }, "examples": { - "add-collection-items-response": { - "$ref": "#/components/examples/add-collection-items-response" + "get-balance-response": { + "$ref": "#/components/examples/get-balance-response" } } } } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "ModelScope API Token" + } + }, + "parameters": { + "owner": { + "name": "owner", + "in": "path", + "description": "仓库 owner(组织或个人)", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "OpenDataLab", + "Qwen", + "iic" + ] + } + }, + "repo_name": { + "name": "repo_name", + "in": "path", + "description": "仓库 repo_name 名称", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "AICC", + "Qwen-Image-Edit-2511" + ] + } + }, + "mcp_server_id": { + "name": "id", + "in": "path", + "description": "服务ID。由平台后台收录的MCP,ID 默认为@author/server_name,当被社区用户认领后将会变更为user_name/server_name;由社区用户自发提交的MCP,ID 为user_name/server_name。", + "required": true, + "schema": { + "type": "string", + "examples": [ + "@modelcontextprotocol/fetch", + "username/server-name" + ] + } + }, + "studio_owner": { + "name": "owner", + "in": "path", + "description": "所有者(用户名或组织名)", + "required": true, + "schema": { + "type": "string", + "examples": [ + "username", + "organization" + ] + } + }, + "studio_repo_name": { + "name": "repo_name", + "in": "path", + "description": "仓库名称", + "required": true, + "schema": { + "type": "string", + "examples": [ + "my-demo-app" + ] + } + }, + "studio_log_type": { + "name": "log_type", + "in": "path", + "description": "日志类型:build(构建日志)或 run(运行日志)", + "required": true, + "schema": { + "type": "string", + "enum": [ + "build", + "run" + ], + "examples": [ + "run" + ] + } + }, + "skill_owner_path": { + "name": "owner", + "in": "path", + "description": "技能作者/所有者,支持 `@author` 格式(官方认证)或 `owner`(用户名或组织名),其中 `@` 无需 URL 编码", + "required": true, + "schema": { + "type": "string", + "minLength": 1 + }, + "examples": { + "author": { + "value": "@iSolver", + "summary": "官方认证作者" + }, + "user": { + "value": "iSolver", + "summary": "用户名或组织名" + } + } + }, + "skill_name_path": { + "name": "skill_name", + "in": "path", + "description": "技能英文名称,仅允许小写字母、数字和连字符", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^[a-z0-9-]+$", + "examples": [ + "my-awesome-skill" + ] + } + }, + "agent_id": { + "name": "id", + "in": "path", + "description": "Agent ID,格式为 path/name(例如 my_org/weather-agent),其中 / 无需 URL 编码", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "my_org/weather-agent" + ] + } + }, + "agent_playground_instance_uuid": { + "name": "uuid", + "in": "path", + "description": "实验场实例 ID", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "51a7d9d2-a521-451a-baf5-367cb115a29a" + ] + } + }, + "collection_owner_path": { + "name": "owner", + "in": "path", + "description": "Collection 所有者(用户名或组织名)。具体长度与格式约束以当前产品页面实现为准。", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "iSolver" + ] + } }, - "update-collection-item-success": { - "description": "成功更新单个合集条目", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCollectionItemResponse" - }, - "examples": { - "update-collection-item-response": { - "$ref": "#/components/examples/update-collection-item-response" - } - } - } + "collection_slug_path": { + "name": "slug", + "in": "path", + "description": "Collection slug 尾段,由 owner 和 title 字段按一定规则自动生成;创建接口会返回该字段。", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "best-nlp-models-6a3b2c" + ] } }, - "remove-collection-item-success": { - "description": "成功移除合集条目", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmptyDataResponse" - }, - "examples": { - "remove-collection-item-response": { - "$ref": "#/components/examples/remove-collection-item-response" - } - } - } + "collection_item_type_path": { + "name": "item_type", + "in": "path", + "description": "资源类型:model / dataset / studio / paper / skill / mcp", + "required": true, + "schema": { + "type": "string", + "enum": [ + "model", + "dataset", + "studio", + "paper", + "skill", + "mcp" + ], + "examples": [ + "model" + ] } }, - "update-collection-items-success": { - "description": "成功批量更新合集条目(部分失败仍返回 success=true,见 failed_items)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCollectionItemsResponse" - }, - "examples": { - "update-collection-items-response": { - "$ref": "#/components/examples/update-collection-items-response" - } - } - } + "collection_item_object_id_path": { + "name": "item_object_id", + "in": "path", + "description": "资源标识,如 damo/nlp_bert_base。可包含 `/`(作为 items/{item_type}/ 之后的整段剩余路径),无需 URL 编码。", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "examples": [ + "damo/nlp_bert_base" + ] } - }, - "create-gallery-success": { - "description": "成功创建 Gallery", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateGalleryResponse" - }, - "examples": { - "create-gallery-response": { - "$ref": "#/components/examples/create-gallery-response" - } - } - } + } + }, + "examples": { + "user-all-response": { + "value": { + "success": true, + "data": { + "username": "tester", + "nickname": "测试用户", + "description": "这是一个测试用户", + "email": "test@modelscope.cn", + "avatar_url": "https://resouces.modelscope.cn/avatar/516aa51f-5fb7-49eb-930c-e90500ae13ca.png" + }, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" } }, - "list-studios-success": { - "description": "成功获取创空间列表", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListStudiosResponse" - } + "list-models-response": { + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "data": { + "models": [ + { + "id": "iic/QwenLong-L1.5-30B-A3B", + "display_name": "QwenLong-L1.5-30B-A3B", + "description": "", + "downloads": 1466, + "likes": 25, + "license": "apache-2.0", + "tasks": [ + "text-generation" + ], + "created_at": "2025-12-14T12:26:15Z", + "last_modified": "2025-12-16T03:34:26Z", + "file_size": 61081671123, + "params": 30532122624, + "tags": [ + "license:apache-2.0", + "model_type:qwen3_moe", + "library:transformer", + "library:safetensors", + "library:pytorch", + "task:text-generation", + "deploy:swingdeploy" + ], + "private": false, + "gated": false + }, + { + "id": "ZhipuAI/AutoGLM-Phone-9B", + "display_name": "AutoGLM-Phone-9B", + "description": "", + "downloads": 28525, + "likes": 265, + "license": "MIT License", + "tasks": [ + "image-captioning" + ], + "created_at": "2025-12-08T08:00:28Z", + "last_modified": "2025-12-09T15:22:57Z", + "file_size": 20605693975, + "params": 5368163840, + "tags": [ + "license:MIT License", + "model_type:glm4v", + "library:safetensors", + "library:pytorch", + "task:image-captioning", + "deploy:swingdeploy" + ], + "private": false, + "gated": false + } + ], + "total_count": 43127, + "page_number": 1, + "page_size": 2 } } }, - "conflict": { - "description": "资源冲突", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "duplicate-entity": { - "$ref": "#/components/examples/duplicate-entity" - } - } + "get-model-response": { + "summary": "普通模型", + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "data": { + "created_at": "2026-09-21T06:36:57Z", + "description": "用于文本生成的模型", + "display_name": "示例模型", + "downloads": 0, + "file_size": 0, + "gated": false, + "id": "alice/my-model", + "last_modified": "2026-09-21T06:37:01Z", + "license": "apache-2.0", + "likes": 0, + "model_category": "base", + "owner": "alice", + "params": 0, + "private": true, + "readme": "# 示例模型\n这里是仓库介绍。", + "repo_name": "my-model", + "tags": [ + "license:apache-2.0" + ], + "tasks": [], + "visibility": "private" } } }, - "too-many-requests": { - "description": "请求过于频繁", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "rate-limit-exceed": { - "$ref": "#/components/examples/rate-limit-exceed" + "list-datasets-response": { + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "data": { + "datasets": [ + { + "id": "DAMO_NLP/jd", + "display_name": "商品评论情感预测", + "description": "该数据集包含从2011年1月1日到2014年3月31日(3年多)某电商网站的消费者购买行为,用户评分,评论和产品元数据,涵盖15个一级产品类别,987个二级产品类别,近2个百万用户,超过10万种产品和超过6,000万条评论。该数据集中的每个文本评论都包含三个子评论:正面评论,负面评论和整体评论。", + "file_size": 0, + "downloads": 53996, + "likes": 102, + "license": "Apache License 2.0", + "tasks": [ + "text-classification" + ], + "created_at": "2022-09-28T06:30:31Z", + "last_modified": "2022-10-30T03:36:39Z", + "tags": [ + "license:Apache License 2.0", + "task:text-classification" + ], + "private": false, + "gated": false, + "login_required": false + }, + { + "id": "iic/nlp_domain_classification_chinese_testset", + "display_name": "中文文本领域分类测试集", + "description": "中文文本领域分类测试集,涉及国民经济行业标准的18个行业,部分收集自公开数据", + "file_size": 0, + "downloads": 2401, + "likes": 23, + "license": "Apache License 2.0", + "tasks": [ + "text-classification" + ], + "created_at": "2023-02-06T09:21:21Z", + "last_modified": "2024-09-02T15:16:35Z", + "tags": [ + "license:Apache License 2.0", + "task:text-classification", + "custom_tag:文本领域", + "custom_tag:领域", + "custom_tag:文本分类" + ], + "private": false, + "gated": false, + "login_required": false } - } + ], + "total_count": 60, + "page_number": 1, + "page_size": 2 } } }, - "get-balance-success": { - "description": "余额查询成功", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetBalanceResponse" - }, - "examples": { - "get-balance-response": { - "$ref": "#/components/examples/get-balance-response" - } - } + "get-dataset-response": { + "summary": "普通数据集", + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "data": { + "created_at": "2025-11-26T11:01:59Z", + "dataset_category": "base", + "description": "示例数据集说明", + "display_name": "示例数据集", + "downloads": 7239, + "file_size": 121668031, + "gated": false, + "id": "alice/my-dataset", + "last_modified": "2026-05-06T12:10:16Z", + "license": "apache-2.0", + "likes": 9, + "login_required": false, + "owner": "alice", + "private": false, + "readme": "# 示例数据集\n这里是仓库介绍。", + "repo_name": "my-dataset", + "tags": [ + "license:apache-2.0", + "custom_tag:RoboCOIN", + "custom_tag:LeRobot" + ], + "tasks": [], + "visibility": "public" } } - } - }, - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "description": "ModelScope API Token" - } - }, - "parameters": { - "owner": { - "name": "owner", - "in": "path", - "description": "仓库 owner(组织或个人)", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "examples": [ - "OpenDataLab", - "Qwen", - "iic" - ] - } }, - "repo_name": { - "name": "repo_name", - "in": "path", - "description": "仓库 repo_name 名称", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "examples": [ - "AICC", - "Qwen-Image-Edit-2511" - ] + "list-mcp-servers-response": { + "value": { + "success": true, + "data": { + "mcp_server_list": [ + { + "id": "@modelcontextprotocol/fetch", + "name": "Fetch网页内容抓取", + "chinese_name": "Fetch网页内容抓取", + "description": "该服务器使大型语言模型能够检索和处理网页内容,支持多种格式的内容提取和转换。", + "logo_url": "https://resources.modelscope.cn/mcp-cover-img/fetch.png", + "publisher": "@modelcontextprotocol/fetch", + "categories": [ + "browser-automation" + ], + "tags": [ + "browser-automation", + "web-scraping" + ], + "view_count": 30667, + "locales": { + "zh": { + "name": "Fetch网页内容抓取", + "description": "该服务器使大型语言模型能够检索和处理网页内容..." + }, + "en": { + "name": "Fetch", + "description": "A server that enables LLMs to retrieve and process web content..." + } + } + }, + { + "id": "@anthropic/claude-mcp", + "name": "Claude MCP", + "chinese_name": "Claude MCP服务", + "description": "Anthropic 官方 MCP 服务,提供 Claude 模型的标准化访问接口。", + "logo_url": "https://resources.modelscope.cn/mcp-cover-img/claude.png", + "publisher": "@anthropic/claude-mcp", + "categories": [ + "ai-assistant" + ], + "tags": [ + "ai-assistant", + "claude" + ], + "view_count": 15234, + "locales": { + "zh": { + "name": "Claude MCP服务", + "description": "Anthropic 官方 MCP 服务..." + }, + "en": { + "name": "Claude MCP", + "description": "Official Anthropic MCP service..." + } + } + } + ], + "total_count": 100 + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "mcp_server_id": { - "name": "id", - "in": "path", - "description": "服务ID。由平台后台收录的MCP,ID 默认为@author/server_name,当被社区用户认领后将会变更为user_name/server_name;由社区用户自发提交的MCP,ID 为user_name/server_name。", - "required": true, - "schema": { - "type": "string", - "examples": [ - "@modelcontextprotocol/fetch", - "username/server-name" - ] + "list-operational-mcp-servers-response": { + "value": { + "success": true, + "data": { + "mcp_server_list": [ + { + "id": "@modelcontextprotocol/fetch", + "name": "Fetch网页内容抓取", + "chinese_name": "Fetch网页内容抓取", + "description": "该服务器使大型语言模型能够检索和处理网页内容...", + "logo_url": "https://resources.modelscope.cn/mcp-cover-img/fetch.png", + "publisher": "@modelcontextprotocol/fetch", + "categories": [ + "browser-automation" + ], + "tags": [ + "browser-automation" + ], + "view_count": 30667, + "operational_urls": [ + { + "id": "platform-pool", + "url": "https://mcp.api-inference.modelscope.net/abc123/mcp", + "transport_type": "streamable_http", + "auth_required": false, + "expiration": "2025-10-01 21:00:00", + "accessible": true + } + ] + } + ], + "total_count": 1 + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "studio_owner": { - "name": "owner", - "in": "path", - "description": "所有者(用户名或组织名)", - "required": true, - "schema": { - "type": "string", - "examples": [ - "username", - "organization" - ] + "get-mcp-server-response": { + "value": { + "success": true, + "data": { + "id": "@modelcontextprotocol/fetch", + "name": "Fetch网页内容抓取", + "chinese_name": "Fetch网页内容抓取", + "description": "该服务器使大型语言模型能够检索和处理网页内容,支持多种格式的内容提取和转换。", + "logo_url": "https://resources.modelscope.cn/mcp-cover-img/fetch.png", + "publisher": "@modelcontextprotocol/fetch", + "author": "modelcontextprotocol", + "owner": "", + "readme": "# Fetch MCP Server\n\nA Model Context Protocol server that provides web content fetching capabilities.\n\n## Features\n\n- Fetch web pages and extract content\n- Support for multiple output formats\n- Automatic content cleaning and formatting", + "source_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch", + "github_stars": 1234, + "is_hosted": true, + "is_verified": true, + "categories": [ + "browser-automation" + ], + "tags": [ + "browser-automation", + "web-scraping" + ], + "view_count": 30667, + "env_schema": {}, + "server_config": [], + "operational_urls": [], + "locales": { + "zh": { + "name": "Fetch网页内容抓取", + "description": "该服务器使大型语言模型能够检索和处理网页内容...", + "readme": "# Fetch MCP Server\n\n提供网页内容抓取能力的 MCP 服务。" + }, + "en": { + "name": "Fetch", + "description": "A server that enables LLMs to retrieve and process web content...", + "readme": "# Fetch MCP Server\n\nA Model Context Protocol server that provides web content fetching capabilities." + } + } + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "studio_repo_name": { - "name": "repo_name", - "in": "path", - "description": "仓库名称", - "required": true, - "schema": { - "type": "string", - "examples": [ - "my-demo-app" - ] + "deploy-mcp-server-response": { + "value": { + "success": true, + "data": { + "id": "platform-pool", + "url": "https://mcp.api-inference.modelscope.net/abc123def456/mcp", + "transport_type": "streamable_http", + "auth_required": false, + "expiration": "2025-10-01 21:00:00" + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "studio_log_type": { - "name": "log_type", - "in": "path", - "description": "日志类型:build(构建日志)或 run(运行日志)", - "required": true, - "schema": { - "type": "string", - "enum": [ - "build", - "run" - ], - "examples": [ - "run" - ] + "undeploy-mcp-server-response": { + "value": { + "success": true, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "skill_owner_path": { - "name": "owner", - "in": "path", - "description": "技能作者/所有者,支持 `@author` 格式(官方认证)或 `owner`(用户名或组织名),其中 `@` 无需 URL 编码", - "required": true, - "schema": { - "type": "string", - "minLength": 1 - }, - "examples": { - "author": { - "value": "@iSolver", - "summary": "官方认证作者" + "create-studio-response": { + "value": { + "success": true, + "data": { + "id": "username/my-demo-app", + "repo_name": "my-demo-app", + "display_name": "我的演示应用", + "owner": "username", + "url": "https://modelscope.cn/studios/username/my-demo-app" }, - "user": { - "value": "iSolver", - "summary": "用户名或组织名" - } + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "skill_name_path": { - "name": "skill_name", - "in": "path", - "description": "技能英文名称,仅允许小写字母、数字和连字符", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 64, - "pattern": "^[a-z0-9-]+$", - "examples": [ - "my-awesome-skill" - ] + "get-studio-response": { + "value": { + "success": true, + "data": { + "id": "username/my-demo-app", + "repo_name": "my-demo-app", + "display_name": "我的演示应用", + "owner": "username", + "description": "一个演示创空间应用", + "likes": 100, + "view_count": 5000, + "tags": [ + "nlp", + "chatbot" + ], + "visibility": "public", + "private": false, + "last_modified": "2025-01-15T10:30:00Z", + "sdk_type": "gradio", + "sdk_version": "6.2.0", + "hardware": "platform/2v-cpu-16g-mem", + "base_image": "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0", + "license": "apache-2.0", + "host": "https://username-my-demo-app.ms.show", + "mcp_support": false, + "runtime": { + "status": "Running", + "active_config": { + "hardware": "platform/2v-cpu-16g-mem", + "base_image": "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0", + "sdk_type": "gradio", + "sdk_version": "6.2.0" + }, + "created_at": "2025-01-15T10:30:00Z" + } + }, + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "collection_owner_path": { - "name": "owner", - "in": "path", - "description": "Collection 所有者(用户名或组织名)。具体长度与格式约束以当前产品页面实现为准。", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "examples": [ - "iSolver" - ] + "studio-runtime-response": { + "value": { + "success": true, + "data": { + "status": "Running", + "active_config": { + "hardware": "platform/2v-cpu-16g-mem", + "base_image": "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0", + "sdk_type": "gradio", + "sdk_version": "6.2.0" + }, + "created_at": "2025-01-15T10:30:00Z" + }, + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "collection_slug_path": { - "name": "slug", - "in": "path", - "description": "Collection slug 尾段,由 owner 和 title 字段按一定规则自动生成;创建接口会返回该字段。", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "examples": [ - "best-nlp-models-6a3b2c" - ] + "get-studio-logs-response": { + "value": { + "success": true, + "data": { + "logs": [ + "2025-01-15 10:30:00 INFO Starting application...", + "2025-01-15 10:30:01 INFO Application started successfully" + ], + "page_num": 1, + "page_size": 100, + "total_count": 500, + "total_page_num": 5 + }, + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "collection_item_type_path": { - "name": "item_type", - "in": "path", - "description": "资源类型:model / dataset / studio / paper / skill / mcp", - "required": true, - "schema": { - "type": "string", - "enum": [ - "model", - "dataset", - "studio", - "paper", - "skill", - "mcp" - ], - "examples": [ - "model" - ] + "list-sdk-versions-response": { + "value": { + "success": true, + "data": { + "sdk_versions": [ + { + "sdk_type": "gradio", + "tag": "latest", + "version": "6.2.0" + }, + { + "sdk_type": "gradio", + "tag": "stable", + "version": "5.49.1" + } + ], + "total_count": 2 + }, + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + } + }, + "list-skills-response": { + "value": { + "success": true, + "request_id": "c5bca202-aa4f-478b-b7e2-c568160583cb", + "data": { + "skills": [ + { + "_id": "CIK3H+DYKlSAMlHnX60AUg==", + "id": "@AMap-Web/amap-lbs-skill", + "display_name": "高德地图综合服务Skill", + "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", + "owner": "", + "license": "MIT License", + "developer": "AMap-Web", + "source_url": "https://github.com/AMap-Web/amap-lbs-skill", + "category": "developer-tools", + "tags": [ + "category:developer-tools", + "license:MIT License", + "developer:AMap-Web", + "custom_tag:api-design", + "custom_tag:general-tools" + ], + "logo_url": "", + "view_count": 6144, + "downloads": 791, + "locales": { + "en": { + "description": "Amap comprehensive services, supporting POI search, route planning, travel planning, nearby search, and heatmap data visualization", + "category": "DevTools" + }, + "zh": { + "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", + "category": "开发工具" + } + }, + "private": false, + "custom_tag": [ + "api-design", + "general-tools" + ] + }, + { + "_id": "0ry9hAiAiJ+2pklbvQjepg==", + "id": "@steipete/summarize", + "display_name": "summarize", + "description": "使用summarize CLI(适用于网页、PDF、图片、音频、YouTube)来总结URL或文件。", + "owner": "steipete", + "license": "", + "developer": "steipete", + "source_url": "https://clawhub.ai/steipete/summarize", + "category": "ai-media", + "tags": [ + "category:ai-media", + "developer:steipete", + "custom_tag:image-video-gen", + "custom_tag:media-processing" + ], + "logo_url": "", + "view_count": 4696, + "downloads": 690, + "locales": { + "en": { + "description": "Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).", + "category": "MediaAI" + }, + "zh": { + "description": "使用summarize CLI(适用于网页、PDF、图片、音频、YouTube)来总结URL或文件。", + "category": "媒体处理" + } + }, + "private": false, + "custom_tag": [ + "image-video-gen", + "media-processing" + ] + } + ], + "total": 20, + "page_number": 1, + "page_size": 20 + } } }, - "collection_item_object_id_path": { - "name": "item_object_id", - "in": "path", - "description": "资源标识,如 damo/nlp_bert_base。可包含 `/`(作为 items/{item_type}/ 之后的整段剩余路径),无需 URL 编码。", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "examples": [ - "damo/nlp_bert_base" - ] + "get-skill-response": { + "value": { + "success": true, + "request_id": "105c898f-fd6d-437d-aa0b-9006036782da", + "data": { + "_id": "CIK3H+DYKlSAMlHnX60AUg==", + "id": "@AMap-Web/amap-lbs-skill", + "display_name": "高德地图综合服务Skill", + "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", + "owner": "", + "license": "MIT License", + "developer": "AMap-Web", + "source_url": "https://github.com/AMap-Web/amap-lbs-skill", + "category": "developer-tools", + "tags": [ + "category:developer-tools", + "license:MIT License", + "developer:AMap-Web", + "custom_tag:api-design", + "custom_tag:general-tools" + ], + "logo_url": "", + "view_count": 6144, + "downloads": 791, + "locales": { + "en": { + "description": "Amap comprehensive services, supporting POI search, route planning, travel planning, nearby search, and heatmap data visualization", + "category": "DevTools" + }, + "zh": { + "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", + "category": "开发工具" + } + }, + "private": false, + "custom_tag": [ + "api-design", + "general-tools" + ], + "install_command": [ + "npx skills add https://modelscope.cn/skills/@AMap-Web/amap-lbs-skill", + "curl -fsSL https://modelscope.cn/skills/install.sh | bash -s -- @AMap-Web/amap-lbs-skill" + ] + } } - } - }, - "examples": { - "user-all-response": { + }, + "create-skill-response": { "value": { "success": true, + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363", "data": { - "username": "tester", - "nickname": "测试用户", - "description": "这是一个测试用户", - "email": "test@modelscope.cn", - "avatar_url": "https://resouces.modelscope.cn/avatar/516aa51f-5fb7-49eb-930c-e90500ae13ca.png" - }, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2" + "id": "iSolver/my-awesome-skill", + "name": "my-awesome-skill", + "display_name": "我的技能", + "owner": "iSolver", + "url": "https://modelscope.cn/skills/iSolver/my-awesome-skill" + } } }, - "list-models-response": { + "update-skill-settings-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + } + }, + "list-agents-response": { + "value": { + "success": true, + "request_id": "c5bca202-aa4f-478b-b7e2-c568160583cb", "data": { - "models": [ + "agents": [ { - "id": "iic/QwenLong-L1.5-30B-A3B", - "display_name": "QwenLong-L1.5-30B-A3B", - "description": "", - "downloads": 1466, - "likes": 25, - "license": "apache-2.0", - "tasks": [ - "text-generation" + "id": "my_org/weather-agent", + "name": "weather-agent", + "owner": "my_org", + "display_name": "天气助手", + "description": "一个可以查询天气的 Agent", + "framework": "ms-agent", + "logo_url": "https://example.com/logo.png", + "license": "Apache License 2.0", + "private": false, + "categories": [ + "tools" + ], + "custom_tags": [ + "weather", + "demo" ], - "created_at": "2025-12-14T12:26:15Z", - "last_modified": "2025-12-16T03:34:26Z", - "file_size": 61081671123, - "params": 30532122624, "tags": [ - "license:apache-2.0", - "model_type:qwen3_moe", - "library:transformer", - "library:safetensors", - "library:pytorch", - "task:text-generation", - "deploy:swingdeploy" + "category:tools", + "license:Apache License 2.0", + "framework:ms-agent", + "custom_tag:weather", + "custom_tag:demo" ], - "private": false, - "gated": false + "views": 319, + "downloads": 15, + "likes": 8, + "last_modified": "2026-07-16T10:30:00Z", + "created_at": "2026-07-01T08:00:00Z" }, { - "id": "ZhipuAI/AutoGLM-Phone-9B", - "display_name": "AutoGLM-Phone-9B", - "description": "", - "downloads": 28525, - "likes": 265, + "id": "zhangsan/deep-research", + "name": "deep-research", + "owner": "zhangsan", + "display_name": "深度研报助手", + "description": "多步检索与研报写作 Agent", + "framework": "qwenpaw", + "logo_url": "", "license": "MIT License", - "tasks": [ - "image-captioning" - ], - "created_at": "2025-12-08T08:00:28Z", - "last_modified": "2025-12-09T15:22:57Z", - "file_size": 20605693975, - "params": 5368163840, + "private": false, + "categories": null, + "custom_tags": null, "tags": [ "license:MIT License", - "model_type:glm4v", - "library:safetensors", - "library:pytorch", - "task:image-captioning", - "deploy:swingdeploy" + "framework:qwenpaw" ], - "private": false, - "gated": false + "views": 149, + "downloads": 3, + "likes": 1, + "last_modified": "2026-07-10T08:12:00Z", + "created_at": "2026-07-02T08:00:00Z" } ], - "total_count": 43127, + "total": 20, "page_number": 1, - "page_size": 2 + "page_size": 10 } } }, - "get-model-response": { + "create-agent-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "2f1c8a3e-9b7d-4f21-a0c6-1e2d3f4a5b6c", "data": { - "id": "Qwen/Qwen-Image-Edit-2511", - "display_name": "Qwen-Image-Edit-2511", - "description": "", - "downloads": 20175, - "likes": 149, - "license": "apache-2.0", - "tasks": [ - "image-to-image" + "id": "my_org/weather-agent", + "name": "weather-agent", + "display_name": "天气助手", + "owner": "my_org", + "url": "https://modelscope.cn/agents/my_org/weather-agent" + } + } + }, + "get-agent-response": { + "value": { + "success": true, + "request_id": "7a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d", + "data": { + "id": "my_org/weather-agent", + "display_name": "天气助手", + "description": "一个可以查询天气的 Agent", + "framework": "ms-agent", + "logo_url": "https://example.com/logo.png", + "license": "Apache License 2.0", + "private": false, + "custom_tags": [ + "weather", + "demo" ], - "created_at": "2025-12-18T01:32:15Z", - "last_modified": "2025-12-23T14:09:52Z", - "file_size": 57720465119, - "params": 0, - "tags": [ - "license:apache-2.0", - "library:pytorch", - "library:safetensors", - "library:diffusers", - "task:image-to-image" + "categories": [ + "tools" ], - "private": false, - "gated": false, - "readme": "hello" + "last_modified": "2026-07-16T10:30:00Z", + "downloads": 15, + "likes": 8, + "mcps": [ + { + "name": "weather-mcp", + "description": "天气查询 MCP", + "tools": null + } + ], + "skills": [ + { + "name": "search-skill", + "description": "搜索技能", + "content": "# Search Skill\n..." + } + ], + "system_files": [ + { + "name": "agent.json", + "content": "{...}" + } + ], + "models": [ + { + "model_name": "qwen-max", + "provider_id": "dashscope", + "base_url": "https://dashscope.aliyuncs.com", + "protocol": "openai", + "provider": "DashScope" + } + ], + "readme": "# Weather Agent\n使用说明..." + } + } + }, + "upload-file-response": { + "value": { + "success": true, + "request_id": "99ee2083-63e0-46dc-b393-623c22645078", + "data": { + "id": "8c378570-8991-431b-a82c-96f3d0b4f0f4" } } }, - "list-datasets-response": { + "create-agent-identity-response": { + "value": { + "success": true, + "data": { + "agent_id": "agent_id:modelscope:agent_1234567890ab", + "agent_name": "my-agent", + "description": "用于演示的测试 Agent", + "token_expire_time": 3600, + "principal": { + "type": "user", + "id": "alice" + }, + "kid": "agent-key-001", + "public_key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs", + "kid": "agent-key-001", + "alg": "EdDSA", + "use": "sig" + }, + "status": "active", + "create_time": "2026-06-08T10:00:00Z" + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + } + }, + "get-agent-identity-response": { + "value": { + "success": true, + "data": { + "agent_id": "agent_id:modelscope:agent_1234567890ab", + "agent_name": "my-agent", + "description": "用于演示的测试 Agent", + "token_expire_time": 3600, + "principal": { + "type": "user", + "id": "alice" + }, + "kid": "agent-key-001", + "public_key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs", + "kid": "agent-key-001", + "alg": "EdDSA", + "use": "sig" + }, + "status": "active", + "create_time": "2026-06-08T10:00:00Z", + "update_time": "2026-06-08T10:30:00Z" + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + } + }, + "update-agent-identity-response": { + "value": { + "success": true, + "data": { + "agent_id": "agent_id:modelscope:agent_1234567890ab", + "agent_name": "my-agent-renamed", + "description": "更新后的描述", + "token_expire_time": 1800, + "principal": { + "type": "user", + "id": "alice" + }, + "kid": "agent-key-001", + "public_key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs", + "kid": "agent-key-001", + "alg": "EdDSA", + "use": "sig" + }, + "status": "active", + "create_time": "2026-06-08T10:00:00Z", + "update_time": "2026-06-08T11:00:00Z" + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + } + }, + "list-agent-identities-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "datasets": [ + "agent_identities": [ { - "id": "DAMO_NLP/jd", - "display_name": "商品评论情感预测", - "description": "该数据集包含从2011年1月1日到2014年3月31日(3年多)某电商网站的消费者购买行为,用户评分,评论和产品元数据,涵盖15个一级产品类别,987个二级产品类别,近2个百万用户,超过10万种产品和超过6,000万条评论。该数据集中的每个文本评论都包含三个子评论:正面评论,负面评论和整体评论。", - "file_size": 0, - "downloads": 53996, - "likes": 102, - "license": "Apache License 2.0", - "tasks": [ - "text-classification" - ], - "created_at": "2022-09-28T06:30:31Z", - "last_modified": "2022-10-30T03:36:39Z", - "tags": [ - "license:Apache License 2.0", - "task:text-classification" - ], - "private": false, - "gated": false, - "login_required": false + "agent_id": "agent_id:modelscope:agent_1234567890ab", + "agent_name": "my-agent", + "kid": "agent-key-001", + "status": "active", + "token_expire_time": 3600, + "create_time": "2026-06-08T10:00:00Z" }, { - "id": "iic/nlp_domain_classification_chinese_testset", - "display_name": "中文文本领域分类测试集", - "description": "中文文本领域分类测试集,涉及国民经济行业标准的18个行业,部分收集自公开数据", - "file_size": 0, - "downloads": 2401, - "likes": 23, - "license": "Apache License 2.0", - "tasks": [ - "text-classification" - ], - "created_at": "2023-02-06T09:21:21Z", - "last_modified": "2024-09-02T15:16:35Z", - "tags": [ - "license:Apache License 2.0", - "task:text-classification", - "custom_tag:文本领域", - "custom_tag:领域", - "custom_tag:文本分类" - ], - "private": false, - "gated": false, - "login_required": false + "agent_id": "agent-cdef01234567", + "agent_name": "build-bot", + "kid": "agent-key-002", + "status": "paused", + "token_expire_time": 1800, + "create_time": "2026-06-07T15:20:00Z" } ], - "total_count": 60, + "total_count": 2, "page_number": 1, - "page_size": 2 - } + "page_size": 20 + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "get-dataset-response": { + "reset-key-pair-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "id": "OpenDataLab/AICC", - "display_name": "AICC", - "description": "", - "file_size": 0, - "downloads": 12119, - "likes": 7, - "license": "cc-by-4.0", - "tasks": [], - "created_at": "2025-11-25T14:30:22Z", - "last_modified": "2026-01-01T00:31:42Z", - "tags": [ - "license:cc-by-4.0", - "custom_tag:common-crawl", - "custom_tag:code", - "custom_tag:markdown", - "custom_tag:math", - "custom_tag:html-parsing" - ], - "private": false, - "gated": false, - "login_required": false, - "readme": "hello" - } + "agent_id": "agent_id:modelscope:agent_1234567890ab", + "kid": "agent-key-002", + "public_key": { + "kty": "OKP", + "crv": "Ed25519", + "x": "UnZsPxBV8ZyK5zhQlpnT1nf5GjxJQbpHQMwoS0jZeNc", + "kid": "agent-key-002", + "alg": "EdDSA", + "use": "sig" + } + }, + "message": "success", + "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "list-mcp-servers-response": { + "pause-agent-response": { "value": { "success": true, - "data": { - "mcp_server_list": [ - { - "id": "@modelcontextprotocol/fetch", - "name": "Fetch网页内容抓取", - "chinese_name": "Fetch网页内容抓取", - "description": "该服务器使大型语言模型能够检索和处理网页内容,支持多种格式的内容提取和转换。", - "logo_url": "https://resources.modelscope.cn/mcp-cover-img/fetch.png", - "publisher": "@modelcontextprotocol/fetch", - "categories": [ - "browser-automation" - ], - "tags": [ - "browser-automation", - "web-scraping" - ], - "view_count": 30667, - "locales": { - "zh": { - "name": "Fetch网页内容抓取", - "description": "该服务器使大型语言模型能够检索和处理网页内容..." - }, - "en": { - "name": "Fetch", - "description": "A server that enables LLMs to retrieve and process web content..." - } - } - }, - { - "id": "@anthropic/claude-mcp", - "name": "Claude MCP", - "chinese_name": "Claude MCP服务", - "description": "Anthropic 官方 MCP 服务,提供 Claude 模型的标准化访问接口。", - "logo_url": "https://resources.modelscope.cn/mcp-cover-img/claude.png", - "publisher": "@anthropic/claude-mcp", - "categories": [ - "ai-assistant" - ], - "tags": [ - "ai-assistant", - "claude" - ], - "view_count": 15234, - "locales": { - "zh": { - "name": "Claude MCP服务", - "description": "Anthropic 官方 MCP 服务..." - }, - "en": { - "name": "Claude MCP", - "description": "Official Anthropic MCP service..." - } - } - } - ], - "total_count": 100 - }, "message": "success", "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "list-operational-mcp-servers-response": { + "delete-agent-identity-response": { "value": { "success": true, - "data": { - "mcp_server_list": [ - { - "id": "@modelcontextprotocol/fetch", - "name": "Fetch网页内容抓取", - "chinese_name": "Fetch网页内容抓取", - "description": "该服务器使大型语言模型能够检索和处理网页内容...", - "logo_url": "https://resources.modelscope.cn/mcp-cover-img/fetch.png", - "publisher": "@modelcontextprotocol/fetch", - "categories": [ - "browser-automation" - ], - "tags": [ - "browser-automation" - ], - "view_count": 30667, - "operational_urls": [ - { - "id": "platform-pool", - "url": "https://mcp.api-inference.modelscope.net/abc123/mcp", - "transport_type": "streamable_http", - "auth_required": false, - "expiration": "2025-10-01 21:00:00", - "accessible": true - } - ] - } - ], - "total_count": 1 - }, "message": "success", "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "get-mcp-server-response": { + "issue-token-response": { "value": { "success": true, "data": { - "id": "@modelcontextprotocol/fetch", - "name": "Fetch网页内容抓取", - "chinese_name": "Fetch网页内容抓取", - "description": "该服务器使大型语言模型能够检索和处理网页内容,支持多种格式的内容提取和转换。", - "logo_url": "https://resources.modelscope.cn/mcp-cover-img/fetch.png", - "publisher": "@modelcontextprotocol/fetch", - "author": "modelcontextprotocol", - "owner": "", - "readme": "# Fetch MCP Server\n\nA Model Context Protocol server that provides web content fetching capabilities.\n\n## Features\n\n- Fetch web pages and extract content\n- Support for multiple output formats\n- Automatic content cleaning and formatting", - "source_url": "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch", - "github_stars": 1234, - "is_hosted": true, - "is_verified": true, - "categories": [ - "browser-automation" - ], - "tags": [ - "browser-automation", - "web-scraping" - ], - "view_count": 30667, - "env_schema": {}, - "server_config": [], - "operational_urls": [], - "locales": { - "zh": { - "name": "Fetch网页内容抓取", - "description": "该服务器使大型语言模型能够检索和处理网页内容...", - "readme": "# Fetch MCP Server\n\n提供网页内容抓取能力的 MCP 服务。" - }, - "en": { - "name": "Fetch", - "description": "A server that enables LLMs to retrieve and process web content...", - "readme": "# Fetch MCP Server\n\nA Model Context Protocol server that provides web content fetching capabilities." - } - } + "access_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0.eyJpc3MiOiJodHRwczovL21vZGVsc2NvcGUuY24iLCJzdWIiOiJhZ2VudC0xMjM0NTY3ODkwYWIiLCJhdWQiOiJodWItYXBwLWFiY2RlZjEyMzQ1NiIsImV4cCI6MTcxNzQxMzYwMCwiaWF0IjoxNzE3NDEwMDAwLCJqdGkiOiJqdGktMTIzNDU2Nzg5MCJ9.dummy_signature_base64url", + "token_type": "Bearer", + "expire_at": 1717413600, + "jti": "jti-1234567890" }, "message": "success", "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "deploy-mcp-server-response": { + "list-token-records-response": { "value": { "success": true, "data": { - "id": "platform-pool", - "url": "https://mcp.api-inference.modelscope.net/abc123def456/mcp", - "transport_type": "streamable_http", - "auth_required": false, - "expiration": "2025-10-01 21:00:00" + "token_records": [ + { + "token_id": "jti-1234567890", + "audience": "hub-app-abcdef123456", + "issued_at": "2026-06-08T11:30:00Z", + "expire_at": "2026-06-08T12:30:00Z", + "status": "active", + "jwt": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0.eyJpc3MiOiJodHRwczovL21vZGVsc2NvcGUuY24iLCJzdWIiOiJhZ2VudC0xMjM0NTY3ODkwYWIiLCJhdWQiOiJodWItYXBwLWFiY2RlZjEyMzQ1NiIsImV4cCI6MTcxNzQxMzYwMCwiaWF0IjoxNzE3NDEwMDAwLCJqdGkiOiJqdGktMTIzNDU2Nzg5MCJ9.dummy_signature_base64url" + }, + { + "token_id": "jti-2345678901", + "audience": "hub-app-fedcba654321", + "issued_at": "2026-06-07T09:15:00Z", + "expire_at": "2026-06-07T10:15:00Z", + "status": "expired", + "jwt": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0.eyJpc3MiOiJodHRwczovL21vZGVsc2NvcGUuY24iLCJzdWIiOiJhZ2VudC0xMjM0NTY3ODkwYWIiLCJhdWQiOiJodWItYXBwLWZlZGNiYTY1NDMyMSIsImV4cCI6MTcxNzMyMjUwMCwiaWF0IjoxNzE3MzE4OTAwLCJqdGkiOiJqdGktMjM0NTY3ODkwMSJ9.dummy_signature_base64url" + } + ], + "total_count": 2, + "page_number": 1, + "page_size": 20 }, "message": "success", "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } }, - "undeploy-mcp-server-response": { + "agentid-configuration-response": { "value": { - "success": true, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "issuer": "https://modelscope.cn", + "token_endpoint": "https://modelscope.cn/openapi/v1/agent_id/token", + "jwks_uri": "https://modelscope.cn/openapi/v1/agent_id/.well-known/agentid-jwks", + "registration_endpoint": "https://modelscope.cn/openapi/v1/agent_ids", + "activity_endpoint": "https://modelscope.cn/openapi/v1/agent_ids/{agent_id}/jwt_id_tokens", + "id_token_signing_alg_values_supported": "EdDSA" } }, - "create-studio-response": { + "agentid-jwks-response": { "value": { - "success": true, - "data": { - "id": "username/my-demo-app", - "repo_name": "my-demo-app", - "display_name": "我的演示应用", - "owner": "username", - "url": "https://modelscope.cn/studios/username/my-demo-app" - }, - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "x": "UnZsPxBV8ZyK5zhQlpnT1nf5GjxJQbpHQMwoS0jZeNc", + "kid": "idp-key-001", + "alg": "EdDSA", + "use": "sig" + }, + { + "kty": "OKP", + "crv": "Ed25519", + "x": "WoxPa7_oZOn1HtdPNn-3Btu7UKmtMVRUyQMlYMM0SnQ", + "kid": "idp-key-002", + "alg": "EdDSA", + "use": "sig" + } + ] } }, - "get-studio-response": { + "get-collection-response": { "value": { "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "id": "username/my-demo-app", - "repo_name": "my-demo-app", - "display_name": "我的演示应用", - "owner": "username", - "description": "一个演示创空间应用", - "likes": 100, - "view_count": 5000, - "tags": [ - "nlp", - "chatbot" - ], + "slug": "iSolver/best-nlp-models-6a3b2c", + "title": "最佳 NLP 模型合集", + "description": "精选的自然语言处理模型...", + "owner": "iSolver", "visibility": "public", - "private": false, - "last_modified": "2025-01-15T10:30:00Z", - "sdk_type": "gradio", - "sdk_version": "6.2.0", - "hardware": "platform/2v-cpu-16g-mem", - "base_image": "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0", - "license": "apache-2.0", - "host": "https://username-my-demo-app.ms.show", - "mcp_support": false, - "runtime": { - "status": "Running", - "active_config": { - "hardware": "platform/2v-cpu-16g-mem", - "base_image": "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0", - "sdk_type": "gradio", - "sdk_version": "6.2.0" + "theme": "Blue", + "item_count": 2, + "likes": 128, + "view_count": 1024, + "items": [ + { + "item_type": "model", + "item_object_id": "damo/nlp_bert_base", + "note": "这个模型在中文 NLP 任务上表现优异", + "position": 1, + "created_at": "2025-01-15T08:00:00Z", + "visibility": "public", + "gated": false, + "protected_mode": 2 }, - "created_at": "2025-01-15T10:30:00Z" - } - }, - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + { + "item_type": "dataset", + "item_object_id": "damo/nlp_corpus", + "note": null, + "position": 2, + "created_at": "2025-01-16T09:00:00Z", + "visibility": "public", + "gated": false, + "protected_mode": 2 + } + ], + "created_at": "2025-01-15T08:00:00Z", + "updated_at": "2025-04-20T10:30:00Z", + "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + } } }, - "studio-runtime-response": { + "create-collection-response": { "value": { "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "status": "Running", - "active_config": { - "hardware": "platform/2v-cpu-16g-mem", - "base_image": "ubuntu22.04-py311-torch2.9.1-modelscope1.35.0", - "sdk_type": "gradio", - "sdk_version": "6.2.0" - }, - "created_at": "2025-01-15T10:30:00Z" - }, - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "slug": "iSolver/best-nlp-models-6a3b2c", + "title": "最佳 NLP 模型合集", + "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + } } }, - "get-studio-logs-response": { + "update-collection-response": { "value": { "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "logs": [ - "2025-01-15 10:30:00 INFO Starting application...", - "2025-01-15 10:30:01 INFO Application started successfully" - ], - "page_num": 1, - "page_size": 100, - "total_count": 500, - "total_page_num": 5 - }, - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "slug": "iSolver/best-nlp-models-renamed-6a3b2c", + "title": "最佳 NLP 模型合集(已更名)", + "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-renamed-6a3b2c" + } } }, - "list-sdk-versions-response": { + "delete-collection-response": { "value": { "success": true, - "data": { - "sdk_versions": [ - { - "sdk_type": "gradio", - "tag": "latest", - "version": "6.2.0" - }, - { - "sdk_type": "gradio", - "tag": "stable", - "version": "5.49.1" - } - ], - "total_count": 2 - }, - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "data": {} } }, - "list-skills-response": { + "list-collections-response": { "value": { "success": true, - "request_id": "c5bca202-aa4f-478b-b7e2-c568160583cb", + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "skills": [ - { - "_id": "CIK3H+DYKlSAMlHnX60AUg==", - "id": "@AMap-Web/amap-lbs-skill", - "display_name": "高德地图综合服务Skill", - "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", - "owner": "", - "license": "MIT License", - "developer": "AMap-Web", - "source_url": "https://github.com/AMap-Web/amap-lbs-skill", - "category": "developer-tools", - "tags": [ - "category:developer-tools", - "license:MIT License", - "developer:AMap-Web", - "custom_tag:api-design", - "custom_tag:general-tools" - ], - "logo_url": "", - "view_count": 6144, - "downloads": 791, - "locales": { - "en": { - "description": "Amap comprehensive services, supporting POI search, route planning, travel planning, nearby search, and heatmap data visualization", - "category": "DevTools" - }, - "zh": { - "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", - "category": "开发工具" - } - }, - "private": false, - "custom_tag": [ - "api-design", - "general-tools" - ] - }, + "collection_list": [ { - "_id": "0ry9hAiAiJ+2pklbvQjepg==", - "id": "@steipete/summarize", - "display_name": "summarize", - "description": "使用summarize CLI(适用于网页、PDF、图片、音频、YouTube)来总结URL或文件。", - "owner": "steipete", - "license": "", - "developer": "steipete", - "source_url": "https://clawhub.ai/steipete/summarize", - "category": "ai-media", - "tags": [ - "category:ai-media", - "developer:steipete", - "custom_tag:image-video-gen", - "custom_tag:media-processing" - ], - "logo_url": "", - "view_count": 4696, - "downloads": 690, - "locales": { - "en": { - "description": "Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).", - "category": "MediaAI" - }, - "zh": { - "description": "使用summarize CLI(适用于网页、PDF、图片、音频、YouTube)来总结URL或文件。", - "category": "媒体处理" - } - }, - "private": false, - "custom_tag": [ - "image-video-gen", - "media-processing" - ] + "slug": "iSolver/best-nlp-models-6a3b2c", + "title": "最佳 NLP 模型合集", + "description": "精选的自然语言处理模型...", + "owner": "iSolver", + "visibility": "public", + "theme": "Blue", + "item_count": 12, + "likes": 128, + "view_count": 1024, + "items": [], + "created_at": "2025-01-15T08:00:00Z", + "updated_at": "2025-04-20T10:30:00Z", + "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" } ], - "total": 20, + "total_count": 128, "page_number": 1, - "page_size": 20 + "page_size": 10 } } }, - "get-skill-response": { + "list-collection-items-response": { "value": { "success": true, - "request_id": "105c898f-fd6d-437d-aa0b-9006036782da", + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "_id": "CIK3H+DYKlSAMlHnX60AUg==", - "id": "@AMap-Web/amap-lbs-skill", - "display_name": "高德地图综合服务Skill", - "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", - "owner": "", - "license": "MIT License", - "developer": "AMap-Web", - "source_url": "https://github.com/AMap-Web/amap-lbs-skill", - "category": "developer-tools", - "tags": [ - "category:developer-tools", - "license:MIT License", - "developer:AMap-Web", - "custom_tag:api-design", - "custom_tag:general-tools" - ], - "logo_url": "", - "view_count": 6144, - "downloads": 791, - "locales": { - "en": { - "description": "Amap comprehensive services, supporting POI search, route planning, travel planning, nearby search, and heatmap data visualization", - "category": "DevTools" - }, - "zh": { - "description": "高德地图综合服务,支持POI搜索、路径规划、旅游规划、周边搜索和热力图数据可视化", - "category": "开发工具" + "items": [ + { + "item_type": "model", + "item_object_id": "damo/nlp_bert_base", + "note": "这个模型在中文 NLP 任务上表现优异", + "position": 1, + "created_at": "2025-01-15T08:00:00Z", + "visibility": "public", + "gated": false, + "protected_mode": 2 } - }, - "private": false, - "custom_tag": [ - "api-design", - "general-tools" ], - "install_command": [ - "npx skills add https://modelscope.cn/skills/@AMap-Web/amap-lbs-skill", - "curl -fsSL https://modelscope.cn/skills/install.sh | bash -s -- @AMap-Web/amap-lbs-skill" - ] + "total_count": 50, + "page_number": 1, + "page_size": 10 } } }, - "create-skill-response": { + "add-collection-items-request": { + "value": { + "items": [ + { + "item_type": "model", + "item_object_id": "damo/nlp_bert_base_chinese", + "note": "推荐用于中文文本分类", + "position": 1 + }, + { + "item_type": "dataset", + "item_object_id": "damo/nlp_corpus" + } + ] + } + }, + "add-collection-items-response": { "value": { "success": true, - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363", + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "id": "iSolver/my-awesome-skill", - "name": "my-awesome-skill", - "display_name": "我的技能", - "owner": "iSolver", - "url": "https://modelscope.cn/skills/iSolver/my-awesome-skill" + "added_count": 2, + "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c", + "failed_items": [ + { + "item_type": "model", + "item_object_id": "not/exist-model", + "reason": "ResourceNotFound" + } + ] } } }, - "update-skill-settings-response": { + "update-collection-item-request": { "value": { - "success": true, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "note": "更新后的备注", + "position": 2 } }, - "upload-file-response": { + "update-collection-item-response": { "value": { "success": true, - "request_id": "99ee2083-63e0-46dc-b393-623c22645078", + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "id": "8c378570-8991-431b-a82c-96f3d0b4f0f4" + "item_type": "model", + "item_object_id": "damo/nlp_bert_base", + "note": "更新后的备注", + "position": 2, + "created_at": "2025-01-15T08:00:00Z", + "visibility": "public", + "gated": false, + "protected_mode": 2 } } }, - "create-agent-identity-response": { + "remove-collection-item-response": { "value": { "success": true, - "data": { - "agent_id": "agent_id:modelscope:agent_1234567890ab", - "agent_name": "my-agent", - "description": "用于演示的测试 Agent", - "token_expire_time": 3600, - "principal": { - "type": "user", - "id": "alice" - }, - "kid": "agent-key-001", - "public_key": { - "kty": "OKP", - "crv": "Ed25519", - "x": "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs", - "kid": "agent-key-001", - "alg": "EdDSA", - "use": "sig" - }, - "status": "active", - "create_time": "2026-06-08T10:00:00Z" - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "data": {} } }, - "get-agent-identity-response": { + "update-collection-items-request": { "value": { - "success": true, - "data": { - "agent_id": "agent_id:modelscope:agent_1234567890ab", - "agent_name": "my-agent", - "description": "用于演示的测试 Agent", - "token_expire_time": 3600, - "principal": { - "type": "user", - "id": "alice" - }, - "kid": "agent-key-001", - "public_key": { - "kty": "OKP", - "crv": "Ed25519", - "x": "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs", - "kid": "agent-key-001", - "alg": "EdDSA", - "use": "sig" + "items": [ + { + "item_type": "model", + "item_object_id": "damo/nlp_bert_base", + "position": 1 }, - "status": "active", - "create_time": "2026-06-08T10:00:00Z", - "update_time": "2026-06-08T10:30:00Z" - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + { + "item_type": "dataset", + "item_object_id": "damo/nlp_corpus", + "note": "更新备注", + "position": 2 + } + ] } }, - "update-agent-identity-response": { + "update-collection-items-response": { "value": { "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", "data": { - "agent_id": "agent_id:modelscope:agent_1234567890ab", - "agent_name": "my-agent-renamed", - "description": "更新后的描述", - "token_expire_time": 1800, - "principal": { - "type": "user", - "id": "alice" - }, - "kid": "agent-key-001", - "public_key": { - "kty": "OKP", - "crv": "Ed25519", - "x": "f7QtzyEJhqFL8Jk5OYOq7010Ry9PipDGQkTzl7XtiEs", - "kid": "agent-key-001", - "alg": "EdDSA", - "use": "sig" - }, - "status": "active", - "create_time": "2026-06-08T10:00:00Z", - "update_time": "2026-06-08T11:00:00Z" - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "updated_count": 2, + "failed_items": [ + { + "item_type": "model", + "item_object_id": "not/exist-model", + "reason": "ItemNotInCollection" + } + ] + } } }, - "list-agent-identities-response": { + "create-gallery-request": { "value": { - "success": true, - "data": { - "agent_identities": [ - { - "agent_id": "agent_id:modelscope:agent_1234567890ab", - "agent_name": "my-agent", - "kid": "agent-key-001", - "status": "active", - "token_expire_time": 3600, - "create_time": "2026-06-08T10:00:00Z" - }, - { - "agent_id": "agent-cdef01234567", - "agent_name": "build-bot", - "kid": "agent-key-002", - "status": "paused", - "token_expire_time": 1800, - "create_time": "2026-06-07T15:20:00Z" - } - ], - "total_count": 2, - "page_number": 1, - "page_size": 20 - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "name": "my-gallery", + "owner": "my-org", + "files": [ + "8c378570-8991-431b-a82c-96f3d0b4f0f4", + "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + ], + "entry_file": "notebook/demo.ipynb", + "private": false, + "category": "notebook", + "labels": [ + "demo", + "tutorial" + ], + "path": "my-custom-path" } }, - "reset-key-pair-response": { + "create-gallery-response": { "value": { "success": true, + "request_id": "req-xxxxxx", "data": { - "agent_id": "agent_id:modelscope:agent_1234567890ab", - "kid": "agent-key-002", - "public_key": { - "kty": "OKP", - "crv": "Ed25519", - "x": "UnZsPxBV8ZyK5zhQlpnT1nf5GjxJQbpHQMwoS0jZeNc", - "kid": "agent-key-002", - "alg": "EdDSA", - "use": "sig" + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "url": "https://modelscope.cn/gallery/my-org/a1b2c3d4-e5f6-7890-abcd-ef1234567890" + } + } + }, + "create-agent-playground-instance-request": { + "value": { + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "framework_type": "qwenpaw", + "models": [ + { + "provider_name": "ModelScope API-Inference", + "model_name": "Qwen/Qwen3.5-27B" } - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + ], + "mcps": [ + { + "name": "amap-maps", + "source": "custom", + "server_config": { + "mcpServers": { + "amap-maps": { + "type": "sse", + "url": "https://mcp.example.com/sse" + } + } + } + } + ], + "skills": [ + "@modelscope/web-search" + ], + "message": "帮我查一下明天的天气" } }, - "pause-agent-response": { + "create-agent-playground-instance-response": { "value": { "success": true, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "request_id": "7d3f8f8a-8c1f-4d0b-8f1a-7b6a0d8a1a11", + "data": { + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "starting", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 0, + "model_count": 0, + "mcp_count": 0, + "skill_count": 0, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-17T15:47:19+08:00", + "user_modified_at": "2026-09-17T15:47:19+08:00" + } } }, - "delete-agent-identity-response": { + "create-agent-playground-instance-from-agent-hub-request": { "value": { - "success": true, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "instance_name": "playground-instance", + "display_name": "Playground Instance", + "description": "从 Agent Hub 导入的实例", + "framework_type": "qwenpaw", + "agent_owner": "jakexiao", + "agent_name": "FirstAgent", + "models": [ + { + "provider_name": "ModelScope API-Inference", + "model_name": "Qwen/Qwen3.5-27B" + } + ] } }, - "issue-token-response": { + "create-agent-playground-instance-from-agent-hub-response": { "value": { "success": true, + "request_id": "1b9d5c7e-2a34-4f6b-8d0e-3c5a7b9d1f24", "data": { - "access_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0.eyJpc3MiOiJodHRwczovL21vZGVsc2NvcGUuY24iLCJzdWIiOiJhZ2VudC0xMjM0NTY3ODkwYWIiLCJhdWQiOiJodWItYXBwLWFiY2RlZjEyMzQ1NiIsImV4cCI6MTcxNzQxMzYwMCwiaWF0IjoxNzE3NDEwMDAwLCJqdGkiOiJqdGktMTIzNDU2Nzg5MCJ9.dummy_signature_base64url", - "token_type": "Bearer", - "expire_at": 1717413600, - "jti": "jti-1234567890" - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "uuid": "8f2c4a1e-7b63-4d95-a0c8-2e6f1b3d5a79", + "owner": "username", + "instance_name": "playground-instance", + "display_name": "Playground Instance", + "description": "从 Agent Hub 导入的实例", + "logo": "", + "status": "starting", + "framework_type": "qwenpaw", + "source": "agent_hub", + "framework_version": "2.2.0", + "agent_count": 0, + "model_count": 0, + "mcp_count": 0, + "skill_count": 0, + "gmt_created": "2026-09-18T10:12:05+08:00", + "gmt_modified": "2026-09-18T10:12:05+08:00", + "user_modified_at": "2026-09-18T10:12:05+08:00" + } } }, - "list-token-records-response": { + "list-agent-playground-instances-response": { "value": { "success": true, + "request_id": "bb971b6e-36f0-4c4c-a50e-ee3fb6d3ceba", "data": { - "token_records": [ + "instances": [ { - "token_id": "jti-1234567890", - "audience": "hub-app-abcdef123456", - "issued_at": "2026-06-08T11:30:00Z", - "expire_at": "2026-06-08T12:30:00Z", - "status": "active", - "jwt": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0.eyJpc3MiOiJodHRwczovL21vZGVsc2NvcGUuY24iLCJzdWIiOiJhZ2VudC0xMjM0NTY3ODkwYWIiLCJhdWQiOiJodWItYXBwLWFiY2RlZjEyMzQ1NiIsImV4cCI6MTcxNzQxMzYwMCwiaWF0IjoxNzE3NDEwMDAwLCJqdGkiOiJqdGktMTIzNDU2Nzg5MCJ9.dummy_signature_base64url" + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "running", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T17:06:19+08:00", + "user_modified_at": "2026-09-17T15:47:19+08:00" }, { - "token_id": "jti-2345678901", - "audience": "hub-app-fedcba654321", - "issued_at": "2026-06-07T09:15:00Z", - "expire_at": "2026-06-07T10:15:00Z", - "status": "expired", - "jwt": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImlkcC1rZXktMDAxIn0.eyJpc3MiOiJodHRwczovL21vZGVsc2NvcGUuY24iLCJzdWIiOiJhZ2VudC0xMjM0NTY3ODkwYWIiLCJhdWQiOiJodWItYXBwLWZlZGNiYTY1NDMyMSIsImV4cCI6MTcxNzMyMjUwMCwiaWF0IjoxNzE3MzE4OTAwLCJqdGkiOiJqdGktMjM0NTY3ODkwMSJ9.dummy_signature_base64url" + "uuid": "8f2c4a1e-7b63-4d95-a0c8-2e6f1b3d5a79", + "owner": "username", + "instance_name": "playground-instance", + "display_name": "Playground Instance", + "description": "", + "logo": "", + "status": "sleeping", + "framework_type": "ms-agent", + "source": "agent_hub", + "framework_version": "1.7.0.dev35316159068", + "agent_count": 1, + "model_count": 1, + "mcp_count": 0, + "skill_count": 0, + "gmt_created": "2026-09-18T10:12:05+08:00", + "gmt_modified": "2026-09-18T18:30:41+08:00", + "user_modified_at": "2026-09-18T10:12:05+08:00" } ], "total_count": 2, "page_number": 1, - "page_size": 20 - }, - "message": "success", - "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" + "page_size": 10 + } } }, - "agentid-configuration-response": { + "get-agent-playground-instance-response": { "value": { - "issuer": "https://modelscope.cn", - "token_endpoint": "https://modelscope.cn/openapi/v1/agent_id/token", - "jwks_uri": "https://modelscope.cn/openapi/v1/agent_id/.well-known/agentid-jwks", - "registration_endpoint": "https://modelscope.cn/openapi/v1/agent_ids", - "activity_endpoint": "https://modelscope.cn/openapi/v1/agent_ids/{agent_id}/jwt_id_tokens", - "id_token_signing_alg_values_supported": "EdDSA" + "success": true, + "request_id": "a4c8e2f1-5b7d-4906-8e3a-1f2c4d6b8a05", + "data": { + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "running", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T17:06:19+08:00", + "user_modified_at": "2026-09-17T15:47:19+08:00" + } } }, - "agentid-jwks-response": { + "update-agent-playground-instance-request": { "value": { - "keys": [ - { - "kty": "OKP", - "crv": "Ed25519", - "x": "UnZsPxBV8ZyK5zhQlpnT1nf5GjxJQbpHQMwoS0jZeNc", - "kid": "idp-key-001", - "alg": "EdDSA", - "use": "sig" - }, - { - "kty": "OKP", - "crv": "Ed25519", - "x": "WoxPa7_oZOn1HtdPNn-3Btu7UKmtMVRUyQMlYMM0SnQ", - "kid": "idp-key-002", - "alg": "EdDSA", - "use": "sig" - } - ] + "instance_name": "my-agent-renamed", + "display_name": "My Agent Renamed", + "description": "Updated description", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png" } }, - "get-collection-response": { + "update-agent-playground-instance-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "6e1a9c3b-8d42-4f07-b5e9-7a0c2d4f6b18", "data": { - "slug": "iSolver/best-nlp-models-6a3b2c", - "title": "最佳 NLP 模型合集", - "description": "精选的自然语言处理模型...", - "owner": "iSolver", - "visibility": "public", - "theme": "Blue", - "item_count": 2, - "likes": 128, - "view_count": 1024, - "items": [ - { - "item_type": "model", - "item_object_id": "damo/nlp_bert_base", - "note": "这个模型在中文 NLP 任务上表现优异", - "position": 1, - "created_at": "2025-01-15T08:00:00Z", - "visibility": "public", - "gated": false, - "protected_mode": 2 - }, - { - "item_type": "dataset", - "item_object_id": "damo/nlp_corpus", - "note": null, - "position": 2, - "created_at": "2025-01-16T09:00:00Z", - "visibility": "public", - "gated": false, - "protected_mode": 2 - } - ], - "created_at": "2025-01-15T08:00:00Z", - "updated_at": "2025-04-20T10:30:00Z", - "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent-renamed", + "display_name": "My Agent Renamed", + "description": "Updated description", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "running", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T17:06:19+08:00", + "user_modified_at": "2026-09-18T17:06:19+08:00" } } }, - "create-collection-response": { + "delete-agent-playground-instance-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "0d2b7a54-1f6c-4a2b-9e33-2c7f9c1d8b40", "data": { - "slug": "iSolver/best-nlp-models-6a3b2c", - "title": "最佳 NLP 模型合集", - "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" + "deleted": true } } }, - "update-collection-response": { + "pause-agent-playground-instance-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "2f8b6d0a-4c19-4e73-9a5f-8b1d3e5c7a92", "data": { - "slug": "iSolver/best-nlp-models-renamed-6a3b2c", - "title": "最佳 NLP 模型合集(已更名)", - "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-renamed-6a3b2c" + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "sleeping", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T18:30:41+08:00", + "user_modified_at": "2026-09-18T18:30:41+08:00" } } }, - "delete-collection-response": { + "resume-agent-playground-instance-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", - "data": {} + "request_id": "9c3e5a7b-1d24-4068-8f0a-6b2d4c8e1f37", + "data": { + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "running", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T18:42:03+08:00", + "user_modified_at": "2026-09-18T18:42:03+08:00" + } } }, - "list-collections-response": { + "restart-agent-playground-instance-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "5a7c9e1b-3f80-4d62-9c4e-0a8b2d6f4c13", "data": { - "collection_list": [ - { - "slug": "iSolver/best-nlp-models-6a3b2c", - "title": "最佳 NLP 模型合集", - "description": "精选的自然语言处理模型...", - "owner": "iSolver", - "visibility": "public", - "theme": "Blue", - "item_count": 12, - "likes": 128, - "view_count": 1024, - "items": [], - "created_at": "2025-01-15T08:00:00Z", - "updated_at": "2025-04-20T10:30:00Z", - "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c" - } - ], - "total_count": 128, - "page_number": 1, - "page_size": 10 + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "starting", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T18:45:27+08:00", + "user_modified_at": "2026-09-18T18:45:27+08:00" } } }, - "list-collection-items-response": { + "get-agent-playground-instance-agents-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "3c9a1b2d-4e5f-6a7b-8c9d-0e1f2a3b4c5d", "data": { - "items": [ + "agents": [ { - "item_type": "model", - "item_object_id": "damo/nlp_bert_base", - "note": "这个模型在中文 NLP 任务上表现优异", - "position": 1, - "created_at": "2025-01-15T08:00:00Z", - "visibility": "public", - "gated": false, - "protected_mode": 2 + "summary": { + "id": "default", + "name": "Default Agent", + "description": "", + "enabled": true, + "models": [ + { + "provider_id": "playground-openai-3f2a9c1d", + "provider": "Playground Openai (3f2a9c1d)", + "model": "Qwen/Qwen3.5-27B", + "type": "openai", + "base_url": "https://api-inference.modelscope.cn/v1", + "enabled": true + } + ] + }, + "detail": { + "id": "default", + "name": "Default Agent", + "description": "", + "models": [ + { + "provider_id": "playground-openai-3f2a9c1d", + "provider": "Playground Openai (3f2a9c1d)", + "model": "Qwen/Qwen3.5-27B", + "type": "openai", + "base_url": "https://api-inference.modelscope.cn/v1", + "enabled": true + } + ], + "persona": { + "AGENTS.md": { + "content": "# AGENTS.md\n\nYou are a helpful agent.", + "enabled": true + }, + "HEARTBEAT.md": { + "content": "", + "enabled": false + } + }, + "skills": [ + { + "name": "web-search", + "description": "", + "enabled": true, + "source": "builtin" + } + ], + "mcp_clients": [ + { + "key": "amap-maps", + "name": "amap-maps", + "enabled": true, + "transport": "sse", + "url": "https://mcp.example.com/sse", + "tools": [ + { + "name": "maps_geo", + "description": "地理编码" + } + ] + } + ] + } } ], - "total_count": 50, - "page_number": 1, - "page_size": 10 + "refreshed_at": 1789722407 } } }, - "add-collection-items-request": { + "setup-agent-playground-model-request": { "value": { - "items": [ + "agent_id": "default", + "provider_name": "ModelScope API-Inference", + "model_name": "Qwen/Qwen3.5-27B" + } + }, + "setup-agent-playground-mcp-request": { + "value": { + "agent_id": "default", + "name": "amap-maps", + "source": "custom", + "server_config": { + "mcpServers": { + "amap-maps": { + "type": "sse", + "url": "https://mcp.example.com/sse" + } + } + } + } + }, + "setup-agent-playground-skill-request": { + "value": { + "agent_id": "default", + "skill_ref": "@modelscope/web-search" + } + }, + "setup-agent-playground-instance-response": { + "value": { + "success": true, + "request_id": "8b4d2f6a-0c57-4e19-a3b8-5d7f9c1e2a64", + "data": { + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "running", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T19:02:11+08:00", + "user_modified_at": "2026-09-17T15:47:19+08:00" + } + } + }, + "import-to-agent-playground-instance-request": { + "value": { + "models": [ { - "item_type": "model", - "item_object_id": "damo/nlp_bert_base_chinese", - "note": "推荐用于中文文本分类", - "position": 1 - }, + "provider_name": "ModelScope API-Inference", + "model_name": "Qwen/Qwen3.5-27B" + } + ], + "mcps": [ { - "item_type": "dataset", - "item_object_id": "damo/nlp_corpus" + "name": "amap-maps", + "source": "custom", + "server_config": { + "mcpServers": { + "amap-maps": { + "type": "sse", + "url": "https://mcp.example.com/sse" + } + } + } } - ] + ], + "skills": [ + "@modelscope/web-search" + ], + "message": "帮我查一下明天的天气" } }, - "add-collection-items-response": { + "import-to-agent-playground-instance-response": { + "value": { + "success": true, + "request_id": "c7e9a1b3-5d02-4f68-9a4c-1b3d5e7f9a02", + "data": { + "uuid": "51a7d9d2-a521-451a-baf5-367cb115a29a", + "owner": "username", + "instance_name": "my-agent", + "display_name": "我的 Agent", + "description": "用于测试的 Agent Playground 实例", + "logo": "https://resources.modelscope.cn/avatar/my-agent.png", + "status": "starting", + "framework_type": "qwenpaw", + "source": "playground", + "framework_version": "2.2.0", + "agent_count": 2, + "model_count": 1, + "mcp_count": 3, + "skill_count": 17, + "gmt_created": "2026-09-17T15:47:19+08:00", + "gmt_modified": "2026-09-18T19:15:38+08:00", + "user_modified_at": "2026-09-18T19:15:38+08:00" + } + } + }, + "get-agent-playground-instance-notifications-response": { "value": { "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", + "request_id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b", "data": { - "added_count": 2, - "url": "https://modelscope.cn/collections/iSolver/best-nlp-models-6a3b2c", - "failed_items": [ + "notifications": [ { - "item_type": "model", - "item_object_id": "not/exist-model", - "reason": "ResourceNotFound" + "id": 7, + "kind": "conversation", + "payload": { + "framework": "qwenpaw", + "agent_id": "default", + "agent_name": "Default", + "message": "帮我查一下明天的天气", + "conversation": { + "framework": "qwenpaw", + "agent_id": "default", + "agent_name": "Default", + "channel": "console", + "session_id": "pg-4150b1f6-chat-0", + "chat_id": "f9ef8968-a0eb-4e70-bca3-d40e93197e85", + "chat_name": "帮我查一下明天的天气" + } + }, + "gmt_created": "2026-07-12T15:32:47+08:00" + }, + { + "id": 8, + "kind": "init_error", + "payload": { + "item": "mcp:2", + "error": "invalid mcp server config", + "code": "InputParameterError" + }, + "gmt_created": "2026-07-12T15:33:02+08:00" } ] } } }, - "update-collection-item-request": { + "agent-playground-input-parameter-error": { "value": { - "note": "更新后的备注", - "position": 2 + "success": false, + "request_id": "bb971b6e-36f0-4c4c-a50e-ee3fb6d3ceba", + "code": "InputParameterError", + "message": "Input parameter is invalid: instance count limit reached (8/8), delete an existing instance first | 输入参数无效: 实例数量已达上限 (8/8),请先删除一个实例" } }, - "update-collection-item-response": { + "agent-playground-invalid-authentication": { "value": { - "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", - "data": { - "item_type": "model", - "item_object_id": "damo/nlp_bert_base", - "note": "更新后的备注", - "position": 2, - "created_at": "2025-01-15T08:00:00Z", - "visibility": "public", - "gated": false, - "protected_mode": 2 - } + "success": false, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "code": "InvalidAuthentication", + "message": "Invalid authentication | 认证无效" } }, - "remove-collection-item-response": { + "agent-playground-operation-not-allowed": { "value": { - "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", - "data": {} + "success": false, + "request_id": "8a1c3e5f-7b92-4d06-8c4a-2e6b0d4f8a13", + "code": "OperationNotAllowed", + "message": "Operation not allowed | 无权执行该操作" } }, - "update-collection-items-request": { + "agent-playground-resource-not-found": { "value": { - "items": [ - { - "item_type": "model", - "item_object_id": "damo/nlp_bert_base", - "position": 1 - }, - { - "item_type": "dataset", - "item_object_id": "damo/nlp_corpus", - "note": "更新备注", - "position": 2 - } - ] + "success": false, + "request_id": "5f7a9c1e-3b0d-4682-9e4f-7c1a3d5b9e02", + "code": "ResourceNotFound", + "message": "Resource not found: instance | 资源不存在: 实例" } }, - "update-collection-items-response": { + "agent-playground-duplicate-entity": { "value": { - "success": true, - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd3", - "data": { - "updated_count": 2, - "failed_items": [ - { - "item_type": "model", - "item_object_id": "not/exist-model", - "reason": "ItemNotInCollection" - } - ] - } + "success": false, + "request_id": "1c3e5a7b-9d02-4f46-8a1b-3d5f7a9c1e04", + "code": "DuplicateEntity", + "message": "instance name already exists: my-agent | 实例名称已存在: my-agent" } }, - "create-gallery-request": { + "agent-playground-internal-server-error": { "value": { - "name": "my-gallery", - "owner": "my-org", - "files": [ - "8c378570-8991-431b-a82c-96f3d0b4f0f4", - "a1b2c3d4-e5f6-7890-abcd-ef1234567890" - ], - "entry_file": "notebook/demo.ipynb", - "private": false, - "category": "notebook", - "labels": [ - "demo", - "tutorial" - ], - "path": "my-custom-path" + "success": false, + "request_id": "9e1b3d5f-7a0c-4248-9b3e-5a7c9e1b3d5f", + "code": "InternalServerError", + "message": "Internal server error, please try again later. | 服务器内部错误,请稍后重试。" } }, - "create-gallery-response": { + "agent-playground-service-unavailable": { "value": { - "success": true, - "request_id": "req-xxxxxx", - "data": { - "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "url": "https://modelscope.cn/gallery/my-org/a1b2c3d4-e5f6-7890-abcd-ef1234567890" - } + "success": false, + "request_id": "2a4c6e8b-0d13-4957-8e2a-4c6e8b0d1357", + "code": "ServiceUnavailable", + "message": "Agent service in the instance is starting, please try again later. | 实例内 Agent 服务启动中,请稍后重试。" + } + }, + "agent-playground-request-timeout": { + "value": { + "success": false, + "request_id": "6b8d0f2a-4c15-4e79-9b1d-3f5a7c9e1b0d", + "code": "RequestTimeout", + "message": "Request timed out, please try again later. | 请求超时,请稍后重试。" } }, "invalid-authentication": { @@ -10071,12 +16685,17 @@ "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd9" } }, - "resource-not-found": { + "model-repo-info-response": { "value": { - "success": false, - "code": "ResourceNotFound", - "message": "Resource not found: model Qwen/Qwen3-72B", - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd5" + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "data": { + "id": "alice/my-anime-lora", + "repo_name": "my-anime-lora", + "display_name": "我的动漫 LoRA", + "owner": "alice", + "url": "https://modelscope.cn/models/alice/my-anime-lora" + } } }, "input-parameter-error": { @@ -10111,6 +16730,187 @@ "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd6" } }, + "resource-not-found": { + "value": { + "success": false, + "code": "ResourceNotFound", + "message": "Resource not found: model Qwen/Qwen3-72B", + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd5" + } + }, + "aigc-model-options-response": { + "summary": "AIGC 配置选项(部分枚举)", + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "data": { + "aigc_types": [ + { + "label": "Checkpoint", + "type": "aigc_type", + "id": "Checkpoint" + }, + { + "label": "LoRA", + "type": "aigc_type", + "id": "LoRA" + }, + { + "label": "VAE", + "type": "aigc_type", + "id": "VAE" + } + ], + "base_architectures": [ + { + "label": "Z-Image", + "subtypes": [ + { + "label": "Z-Image-Turbo", + "type": "base_architecture_subtype", + "id": "Z_IMAGE_TURBO" + }, + { + "label": "Z-Image", + "type": "base_architecture_subtype", + "id": "Z_IMAGE" + } + ], + "type": "base_architecture", + "id": "Z_IMAGE_TURBO" + }, + { + "label": "Wan2.2", + "subtypes": [ + { + "label": "Wan2.2-TI2V-5B", + "type": "base_architecture_subtype", + "id": "WAN_VIDEO_2_2_TI2V_5_B" + } + ], + "type": "base_architecture", + "id": "WAN_VIDEO_2_2_TI2V_5_B" + }, + { + "label": "Ace-Step", + "subtypes": [ + { + "label": "Acestep-Xl-Sft", + "type": "base_architecture_subtype", + "id": "ACESTEP_XL_SFT" + }, + { + "label": "Acestep-Xl-Turbo", + "type": "base_architecture_subtype", + "id": "ACESTEP_XL_TURBO" + } + ], + "type": "base_architecture", + "id": "ACESTEP_XL" + } + ], + "content_elements": [ + { + "label": "logo图标", + "type": "content_element", + "id": "logo" + }, + { + "label": "商品", + "type": "content_element", + "id": "commodity" + } + ], + "content_functions": [ + { + "label": "人物加强", + "type": "content_function", + "id": "character-enhancement" + }, + { + "label": "画风加强", + "type": "content_function", + "id": "art-style-strong" + } + ], + "content_styles": [ + { + "label": "写实摄影", + "type": "content_style", + "id": "photography" + }, + { + "label": "插画设计", + "type": "content_style", + "id": "illustration-design" + } + ] + } + } + }, + "rate-limit-exceed": { + "value": { + "success": false, + "code": "RateLimitExceed", + "message": "Rate limit exceeded: 100 requests per minute", + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd7" + } + }, + "science-model-options-response": { + "summary": "科学模型学科选项(部分枚举)", + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "data": { + "subject_areas": [ + { + "label": "生命科学", + "specializations": [ + { + "label": "医学", + "type": "specialization", + "id": "medicine" + }, + { + "label": "RNA", + "type": "specialization", + "id": "rna" + }, + { + "label": "DNA", + "type": "specialization", + "id": "dna" + } + ], + "type": "subject_area", + "id": "life-sciences" + }, + { + "label": "物质科学", + "specializations": [ + { + "label": "分子结构", + "type": "specialization", + "id": "molecular-structure" + }, + { + "label": "材料结构", + "type": "specialization", + "id": "material-structure" + }, + { + "label": "化学反应", + "type": "specialization", + "id": "chemical-reaction" + } + ], + "type": "subject_area", + "id": "physical-science" + } + ] + } + } + }, "skill-input-parameter-error": { "value": { "success": false, @@ -10159,12 +16959,60 @@ "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375be2" } }, - "rate-limit-exceed": { + "agent-input-parameter-error": { "value": { "success": false, - "code": "RateLimitExceed", - "message": "Rate limit exceeded: 100 requests per minute", - "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd7" + "code": "InputParameterError", + "message": "Input parameter is invalid: invalid request body", + "request_id": "2f1c8a3e-9b7d-4f21-a0c6-1e2d3f4a5b6c" + } + }, + "agent-invalid-authentication": { + "value": { + "success": false, + "code": "InvalidAuthentication", + "message": "Invalid authentication: user not authenticated", + "request_id": "2f1c8a3e-9b7d-4f21-a0c6-1e2d3f4a5b6c" + } + }, + "agent-quota-limit-exceed": { + "value": { + "success": false, + "code": "QuotaLimitExceed", + "message": "Quota limit exceeded: quota limit exceed 2400 agents", + "request_id": "c5bca202-aa4f-478b-b7e2-c568160583cb" + } + }, + "agent-list-failed": { + "value": { + "success": false, + "code": "ResourceNotFound", + "message": "Resource not found: failed to list agents", + "request_id": "c5bca202-aa4f-478b-b7e2-c568160583cb" + } + }, + "agent-internal-server-error": { + "value": { + "success": false, + "code": "InternalServerError", + "message": "Internal server error: failed to create agent", + "request_id": "2f1c8a3e-9b7d-4f21-a0c6-1e2d3f4a5b6c" + } + }, + "agent-operation-not-allowed": { + "value": { + "success": false, + "code": "OperationNotAllowed", + "message": "Operation not allowed: no permission to access this agent", + "request_id": "7a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" + } + }, + "agent-not-found": { + "value": { + "success": false, + "code": "ResourceNotFound", + "message": "Resource not found: agent my_org/weather-agent", + "request_id": "7a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d" } }, "get-balance-response": { @@ -10177,7 +17025,62 @@ }, "request_id": "c55709d2-4e09-4bd8-be84-d8518bc46363" } + }, + "science-dataset-options-response": { + "summary": "科学数据集学科选项(部分枚举)", + "value": { + "success": true, + "request_id": "4d3f4b7d-95be-4e6f-95eb-d75178375bd2", + "data": { + "subject_areas": [ + { + "label": "生命科学", + "specializations": [ + { + "label": "医学", + "type": "specialization", + "id": "medicine" + }, + { + "label": "RNA", + "type": "specialization", + "id": "rna" + }, + { + "label": "DNA", + "type": "specialization", + "id": "dna" + } + ], + "type": "subject_area", + "id": "life-sciences" + }, + { + "label": "物质科学", + "specializations": [ + { + "label": "分子结构", + "type": "specialization", + "id": "molecular-structure" + }, + { + "label": "材料结构", + "type": "specialization", + "id": "material-structure" + }, + { + "label": "化学反应", + "type": "specialization", + "id": "chemical-reaction" + } + ], + "type": "subject_area", + "id": "physical-science" + } + ] + } + } } } } -} +} \ No newline at end of file diff --git a/tests/test_compat_get_model_files.py b/tests/test_compat_get_model_files.py index 6f16696..216a047 100644 --- a/tests/test_compat_get_model_files.py +++ b/tests/test_compat_get_model_files.py @@ -51,6 +51,31 @@ def _fake_files(): ] +@pytest.mark.parametrize("name", ["approval_mode", "gated"]) +@pytest.mark.parametrize("value", [None, "manual"]) +def test_legacy_aigc_rejects_removed_parameters_before_upload(name, value): + lha = LegacyHubApi(token="ms-test") + aigc = _FakeAigcModel() + with pytest.raises(TypeError, match=name): + lha.create_model("owner/repo", aigc_model=aigc, visibility=1, gated_mode=True, **{name: value}) + aigc.preupload_weights.assert_not_called() + aigc.upload_to_repo.assert_not_called() + + +@pytest.mark.parametrize("method", ["create_repo", "create_model", "update_model", "update_dataset"]) +@pytest.mark.parametrize("name", ["approval_mode", "gated"]) +def test_plain_compat_wrappers_reject_removed_request_parameters(method, name): + lha = LegacyHubApi(token="ms-test") + with ( + mock.patch.object(lha._api.legacy, "create_repo") as legacy, + mock.patch.object(lha._api.openapi, "_request") as native, + ): + with pytest.raises(TypeError, match=name): + getattr(lha, method)("owner/repo", **{name: None}) + legacy.assert_not_called() + native.assert_not_called() + + class TestGetModelFilesLegacyCompat: def test_revision_is_accepted_and_forwarded(self): lha = LegacyHubApi() @@ -149,31 +174,44 @@ def test_aigc_model_uploads_optional_readme_after_creation(self): api = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") aigc_model = _FakeAigcModel() - with mock.patch.object(api._api.legacy, "_request", return_value=_response()) as request: + with ( + mock.patch.object(api._api.legacy, "_request", return_value=_response()) as legacy_request, + mock.patch.object(api._api.openapi, "_request", return_value={}) as openapi_request, + ): api.create_model( "owner/aigc-model", aigc_model=aigc_model, readme_content="# Initial AIGC README\n", ) - assert request.call_count == 3 - create_call, ensure_repo_call, readme_commit_call = request.call_args_list + # The AIGC repository itself still goes through the dedicated legacy + # endpoint: no OpenAPI route accepts its payload. + assert legacy_request.call_count == 2 + create_call, readme_commit_call = legacy_request.call_args_list assert create_call.args == ("POST", "models/aigc") - assert ensure_repo_call.args == ("POST", "models") assert readme_commit_call.args == ( "POST", "repos/models/owner/aigc-model/commit/master", ) + # Only the uploader's "ensure the repo exists" step moved to OpenAPI. + assert [call.args for call in openapi_request.call_args_list] == [("POST", "/models")] commit_body = readme_commit_call.kwargs["json_body"] assert commit_body["commit_message"] == "Update README.md for AIGC version v1.0" operation = commit_body["actions"][0] assert operation["path"] == "README.md" assert operation["content"] == "IyBJbml0aWFsIEFJR0MgUkVBRE1FCg==" - def test_plain_model_keeps_generic_create_repo_path(self): + def test_plain_model_uses_the_openapi_create_endpoint(self): api = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") - with mock.patch.object(api._api.legacy, "_request", return_value=_response()) as request: + with ( + mock.patch.object( + api._api.openapi, + "_request", + return_value={"id": "owner/plain-model", "owner": "owner", "repo_name": "plain-model"}, + ) as openapi_request, + mock.patch.object(api._api.legacy, "_request", return_value=_response()) as legacy_request, + ): url = api.create_model( "owner/plain-model", visibility=1, @@ -182,16 +220,36 @@ def test_plain_model_keeps_generic_create_repo_path(self): aigc_model=None, ) - request.assert_called_once() - method, path = request.call_args.args - body = request.call_args.kwargs["json_body"] - assert (method, path) == ("POST", "models") - assert body["Path"] == "owner" - assert body["Name"] == "plain-model" + legacy_request.assert_not_called() + openapi_request.assert_called_once() + assert openapi_request.call_args.args == ("POST", "/models") + body = openapi_request.call_args.kwargs["json_body"] + assert body == { + "owner": "owner", + "repo_name": "plain-model", + "visibility": "private", + "display_name": "普通模型", + "license": "apache-2.0", + } assert "TagShowName" not in body assert "aigc_model" not in body assert url == "https://modelscope.cn/models/owner/plain-model" + def test_plain_model_falls_back_to_legacy_for_an_unlistable_license(self): + """``POST /models`` rejects licenses outside its enum; legacy accepts them.""" + api = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + + with ( + mock.patch.object(api._api.openapi, "_request") as openapi_request, + mock.patch.object(api._api.legacy, "_request", return_value=_response()) as legacy_request, + ): + api.create_model("owner/plain-model", license="Unlicense", aigc_model=None) + + openapi_request.assert_not_called() + legacy_request.assert_called_once() + assert legacy_request.call_args.args == ("POST", "models") + assert legacy_request.call_args.kwargs["json_body"]["License"] == "unlicense" + def test_aigc_model_tag_uses_dedicated_endpoint(self): api = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") aigc_model = _FakeAigcModel() @@ -257,16 +315,19 @@ def test_aigc_model_tag_uploads_readme_before_snapshot(self): aigc_model = _FakeAigcModel() aigc_model.readme_content = "# Version v1.3\n\nCustom README content.\n" - with mock.patch.object(api._api.legacy, "_request", return_value=_response()) as request: + with ( + mock.patch.object(api._api.legacy, "_request", return_value=_response()) as legacy_request, + mock.patch.object(api._api.openapi, "_request", return_value={}), + ): api.create_model_tag( "owner/aigc-model", "v1.3", aigc_model=aigc_model, ) - assert request.call_count == 3 - create_repo_call, readme_commit_call, tag_call = request.call_args_list - assert create_repo_call.args == ("POST", "models") + # The README must be committed before the immutable tag snapshot. + assert legacy_request.call_count == 2 + readme_commit_call, tag_call = legacy_request.call_args_list assert readme_commit_call.args == ( "POST", "repos/models/owner/aigc-model/commit/master", @@ -286,7 +347,10 @@ def test_explicit_readme_content_overrides_aigc_model_value(self): aigc_model = _FakeAigcModel() aigc_model.readme_content = "model value" - with mock.patch.object(api._api.legacy, "_request", return_value=_response()) as request: + with ( + mock.patch.object(api._api.legacy, "_request", return_value=_response()) as legacy_request, + mock.patch.object(api._api.openapi, "_request", return_value={}), + ): api.create_model_tag( "owner/aigc-model", "v1.3", @@ -294,7 +358,7 @@ def test_explicit_readme_content_overrides_aigc_model_value(self): readme_content="explicit value", ) - commit_body = request.call_args_list[1].kwargs["json_body"] + commit_body = legacy_request.call_args_list[0].kwargs["json_body"] operation = commit_body["actions"][0] assert operation["content"] == "ZXhwbGljaXQgdmFsdWU=" diff --git a/tests/test_create_repo_routing.py b/tests/test_create_repo_routing.py new file mode 100644 index 0000000..c8598f7 --- /dev/null +++ b/tests/test_create_repo_routing.py @@ -0,0 +1,420 @@ +"""Routing of ``create_repo`` between the OpenAPI and legacy create endpoints. + +The OpenAPI schemas are closed: ``visibility`` has no ``internal``, +``POST /models`` restricts ``license`` to a published set, and both endpoints +answer 400 for undeclared fields. Inputs the schema cannot express therefore +keep using the legacy endpoint, so a call that works today keeps working. These +tests pin which surface each input lands on, asserting the method and path +actually sent rather than a mock's return value. +""" + +from __future__ import annotations + +from unittest import mock + +import pytest + +from modelscope_hub import HubApi +from modelscope_hub import api as api_module +from modelscope_hub.constants import RepoType, Visibility +from modelscope_hub.errors import AlreadyExistsError, InvalidParameter, NotExistError, ServerError + + +@pytest.fixture +def api() -> HubApi: + return HubApi(endpoint="https://modelscope.cn", token="ms-test") + + +def _legacy_response(data=None): + response = mock.MagicMock() + response.json.return_value = {"Data": data or {}} + return response + + +class _Routes: + """Captures which surface a create call reached, and with what body.""" + + def __init__(self, openapi_request, legacy_request): + self.openapi = openapi_request + self.legacy = legacy_request + + @property + def went_openapi(self) -> bool: + return self.openapi.called and not self.legacy.called + + @property + def went_legacy(self) -> bool: + return self.legacy.called and not self.openapi.called + + @property + def openapi_body(self) -> dict: + return self.openapi.call_args.kwargs["json_body"] + + @property + def legacy_body(self) -> dict: + kwargs = self.legacy.call_args.kwargs + if "json_body" in kwargs: + return kwargs["json_body"] + # The dataset endpoint posts multipart form fields. + return {k: v[1] for k, v in kwargs["files"].items()} + + +@pytest.fixture +def routes(api: HubApi): + created = {"id": "alice/thing", "owner": "alice", "repo_name": "thing"} + with ( + mock.patch.object(api.openapi, "_request", return_value=created) as openapi_request, + mock.patch.object(api.legacy, "_request", return_value=_legacy_response(created)) as legacy_request, + ): + yield _Routes(openapi_request, legacy_request) + + +# --------------------------------------------------------------------------- +# Inputs the OpenAPI schema expresses +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("repo_type", ["model", "dataset"]) +@pytest.mark.parametrize( + ("visibility", "expected"), + [ + (None, None), + ("public", "public"), + ("private", "private"), + (Visibility.PUBLIC, "public"), + (Visibility.PRIVATE, "private"), + (1, "private"), + (5, "public"), + ("1", "private"), + ], +) +def test_ordinary_visibility_uses_openapi(api, routes, repo_type, visibility, expected): + api.create_repo("alice/thing", repo_type, visibility=visibility) + + assert routes.went_openapi + assert routes.openapi.call_args.args == ("POST", f"/{repo_type}s") + assert routes.openapi_body.get("visibility") == expected + + +def test_license_in_the_enum_uses_openapi(api, routes): + api.create_repo("alice/thing", "model", license="apache-2.0") + + assert routes.went_openapi + assert routes.openapi_body["license"] == "apache-2.0" + + +def test_display_name_license_other_and_task_reach_openapi(api, routes): + api.create_repo( + "alice/thing", + "model", + chinese_name="我的模型", + description="desc", + license="other", + license_name="Custom", + license_link="https://example.com/LICENSE", + task="text-generation", + ) + + assert routes.went_openapi + assert routes.openapi_body == { + "owner": "alice", + "repo_name": "thing", + "display_name": "我的模型", + "description": "desc", + "license": "other", + "license_name": "Custom", + "license_link": "https://example.com/LICENSE", + "task": "text-generation", + } + + +def test_omitted_license_is_not_sent(api, routes): + """The legacy fallback value ``Apache-2.0`` is outside the OpenAPI enum.""" + api.create_repo("alice/thing", "model") + + assert routes.went_openapi + assert "license" not in routes.openapi_body + + +def test_task_is_rejected_for_datasets(api, routes): + """Kept as a defensive guard; datasets are routed to legacy before this.""" + with pytest.raises(InvalidParameter, match="task is only supported"): + api._create_via_openapi( + RepoType.DATASET, + "alice", + "thing", + visibility=None, + license=None, + chinese_name=None, + description=None, + license_name=None, + license_link=None, + task="text-classification", + extra={}, + ) + + +# --------------------------------------------------------------------------- +# Gated access +# --------------------------------------------------------------------------- +def test_legacy_boolean_gated_mode_keeps_the_legacy_wire_format(api, routes): + """The historical boolean must produce the request it always produced. + + This pins the historical request format, not a new approval policy. + """ + api.create_repo("alice/thing", "model", gated_mode=True) + + assert routes.went_legacy + body = routes.legacy_body + assert body["Visibility"] == int(Visibility.PRIVATE) + assert body["ProtectedMode"] == 1 + + +def test_gated_mode_false_keeps_the_legacy_wire_format(api, routes): + """Preserves the legacy behaviour of ``--no-gated`` with no visibility.""" + api.create_repo("alice/thing", "model", gated_mode=False) + + assert routes.went_legacy + body = routes.legacy_body + assert body["Visibility"] == int(Visibility.PRIVATE) + assert body["ProtectedMode"] == 2 + + +def test_gated_mode_with_public_visibility_warns_and_is_ignored(api, routes, caplog, monkeypatch): + """Unchanged legacy behaviour: warn, ignore the gating, stay public.""" + monkeypatch.setattr(api_module.logger, "handlers", [*api_module.logger.handlers, caplog.handler]) + with caplog.at_level("WARNING", logger="modelscope_hub.api"): + api.create_repo("alice/thing", "model", visibility="public", gated_mode=True) + + assert routes.went_legacy + body = routes.legacy_body + assert body["Visibility"] == int(Visibility.PUBLIC) + assert "ProtectedMode" not in body + assert "only effective when visibility is PRIVATE" in caplog.text + + +@pytest.mark.parametrize("repo_type", ["model", "dataset"]) +def test_native_gated_visibility_is_not_a_high_level_input(api, routes, repo_type): + with pytest.raises(ValueError, match="Unknown visibility"): + api.create_repo("alice/thing", repo_type, visibility="gated") + assert not routes.openapi.called and not routes.legacy.called + + +# --------------------------------------------------------------------------- +# Inputs the OpenAPI schema cannot express -> legacy +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("visibility", ["internal", Visibility.INTERNAL, 3, "3"]) +def test_internal_visibility_falls_back_to_legacy(api, routes, visibility): + api.create_repo("alice/thing", "model", visibility=visibility) + + assert routes.went_legacy + assert routes.legacy.call_args.args == ("POST", "models") + assert routes.legacy_body["Visibility"] == int(Visibility.INTERNAL) + + +@pytest.mark.parametrize("license_value", ["bsd-2-clause", "unlicense", "my-company-eula"]) +def test_a_model_license_outside_the_enum_falls_back_to_legacy(api, routes, license_value): + api.create_repo("alice/thing", "model", license=license_value) + + assert routes.went_legacy + assert routes.legacy_body["License"] == license_value + + +def test_a_dataset_license_is_unconstrained_and_stays_on_openapi(api, routes): + """``POST /datasets`` declares no license enum, unlike ``POST /models``.""" + api.create_repo("alice/thing", "dataset", license="my-company-eula") + + assert routes.went_openapi + assert routes.openapi_body["license"] == "my-company-eula" + + +def test_an_undeclared_extra_field_falls_back_to_legacy(api, routes): + api.create_repo("alice/thing", "model", create_default_config=True) + + assert routes.went_legacy + assert routes.legacy_body["create_default_config"] is True + + +def test_protected_visibility_is_rejected_for_models(api, routes): + with pytest.raises(ValueError): + api.create_repo("alice/thing", "model", visibility="protected") + + +def test_task_is_routed_to_legacy_for_datasets(api, routes): + """Only the model schema declares ``task``; legacy took it through extra.""" + api.create_repo("alice/thing", "dataset", task="text-classification") + + assert routes.went_legacy + assert routes.legacy_body["task"] == "text-classification" + + +def test_the_newly_named_params_keep_their_legacy_key_names(api, routes): + """They were ``extra`` keys before the signature grew; renaming them on the + legacy wire would change a request that used to work.""" + api.create_repo( + "alice/thing", + "model", + gated_mode=True, # forces the legacy route + task="text-generation", + license_name="Custom", + license_link="https://example.com/LICENSE", + ) + + assert routes.went_legacy + body = routes.legacy_body + # Appended verbatim, after ProtectedMode, exactly as ``**extra`` always was. + assert [k for k in body if k not in ("Path", "Name", "Visibility", "License")] == [ + "ProtectedMode", + "task", + "license_name", + "license_link", + ] + assert "Task" not in body + assert "LicenseName" not in body + assert "LicenseLink" not in body + + +# --------------------------------------------------------------------------- +# Endpoint availability +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("status", [404, 501]) +def test_an_unpublished_endpoint_degrades_to_legacy(api, status): + error = NotExistError("absent", status_code=status) if status == 404 else ServerError("absent", status_code=status) + with ( + mock.patch.object(api.openapi, "_request", side_effect=error) as openapi_request, + mock.patch.object(api.legacy, "_request", return_value=_legacy_response()) as legacy_request, + ): + api.create_repo("alice/thing", "model") + + assert openapi_request.called + assert legacy_request.call_args.args == ("POST", "models") + + +def test_other_server_errors_are_not_swallowed(api): + with ( + mock.patch.object(api.openapi, "_request", side_effect=ServerError("boom", status_code=500)), + mock.patch.object(api.legacy, "_request", return_value=_legacy_response()) as legacy_request, + ): + with pytest.raises(ServerError): + api.create_repo("alice/thing", "model") + + legacy_request.assert_not_called() + + +def test_a_duplicate_dataset_reported_as_400_becomes_already_exists(api): + """``POST /datasets`` declares no 409, so callers would otherwise miss it.""" + rejection = InvalidParameter("dataset already exists", status_code=400) + with mock.patch.object(api.openapi, "_request", side_effect=rejection): + with pytest.raises(AlreadyExistsError): + api.create_repo("alice/thing", "dataset") + + +def test_a_genuine_bad_request_stays_invalid_parameter(api): + with mock.patch.object(api.openapi, "_request", side_effect=InvalidParameter("bad name", status_code=400)): + with pytest.raises(InvalidParameter) as excinfo: + api.create_repo("alice/thing", "dataset") + assert not isinstance(excinfo.value, AlreadyExistsError) + + +# --------------------------------------------------------------------------- +# Returned metadata +# --------------------------------------------------------------------------- +def test_request_values_backfill_the_sparse_create_response(api, routes): + """The create response carries identity only; legacy echoed the settings.""" + info = api.create_repo( + "alice/thing", + "model", + visibility="private", + license="mit", + description="desc", + ) + + assert info.repo_id == "alice/thing" + assert info.visibility == Visibility.PRIVATE + assert info.license == "mit" + assert info.description == "desc" + assert info.gated is False + + +@pytest.mark.parametrize( + ("kwargs", "expected"), + [ + ({"visibility": "private"}, Visibility.PRIVATE), + ({"visibility": "public"}, Visibility.PUBLIC), + ({"visibility": 5}, Visibility.PUBLIC), + ({"visibility": "1"}, Visibility.PRIVATE), + ], +) +def test_the_backfilled_visibility_is_an_enum_not_a_bare_int(api, routes, kwargs, expected): + """Assigned after ``__init__``, so it bypasses the dataclass's coercion. + + ``1 == Visibility.PRIVATE`` holds for a plain int, so this asserts on + ``.label`` -- which only exists on the enum. + """ + info = api.create_repo("alice/thing", "model", **kwargs) + + assert isinstance(info.visibility, Visibility) + assert info.visibility.label == expected.label + + +def test_native_gated_response_preserves_historical_visibility_type(api): + with mock.patch.object( + api.openapi, + "get_model", + return_value={"visibility": "gated", "private": False, "gated": True, "gated_mode": "auto"}, + ): + info = api.get_repo("alice/thing", "model") + assert isinstance(info.visibility, Visibility) and info.visibility == Visibility.PRIVATE + assert info.gated is True and info.gated_mode == "auto" + assert not hasattr(info, "approval_mode") + + +# Preserve the v0.5.1 switch/visibility contract rather than inventing aliases. +@pytest.mark.parametrize("repo_type", ["model", "dataset"]) +@pytest.mark.parametrize("switch", [True, False]) +@pytest.mark.parametrize("visibility", [None, "private", Visibility.PRIVATE, "public", "internal"]) +def test_historical_switch_matrix_is_unchanged(api, routes, repo_type, switch, visibility): + import warnings + + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter("always") + api.create_repo("alice/thing", repo_type, visibility=visibility, gated_mode=switch) + assert routes.went_legacy + vis = ( + int(Visibility.PRIVATE) + if visibility is None + else int(Visibility.from_label(visibility)) + if isinstance(visibility, str) + else int(visibility) + ) + assert int(routes.legacy_body["Visibility"]) == vis + if vis == int(Visibility.PRIVATE): + assert int(routes.legacy_body["ProtectedMode"]) == (1 if switch else 2) + else: + assert "ProtectedMode" not in routes.legacy_body + assert not any(issubclass(w.category, DeprecationWarning) for w in recorded) + assert "approval_mode" not in routes.legacy_body + + +@pytest.mark.parametrize("repo_type", ["model", "dataset", "studio", "skill"]) +@pytest.mark.parametrize("name", ["approval_mode", "gated"]) +@pytest.mark.parametrize("value", [None, False, "manual"]) +def test_removed_parameters_cannot_leak_through_extra(api, routes, repo_type, name, value): + with pytest.raises(TypeError, match=name): + api.create_repo("alice/thing", repo_type, **{name: value}) + assert not routes.openapi.called and not routes.legacy.called + + +@pytest.mark.parametrize( + "method", + ["create_repo", "_create_model_or_dataset", "_create_via_openapi", "_create_via_legacy", "_openapi_visibility"], +) +def test_request_signatures_have_no_removed_aliases(method): + import inspect + + assert not {"approval_mode", "gated"} & set(inspect.signature(getattr(HubApi, method)).parameters) + + +@pytest.mark.parametrize("bad", ["auto", "manual", 1]) +def test_historical_switch_is_not_an_approval_string(api, routes, bad): + with pytest.raises(InvalidParameter, match="gated_mode must be"): + api.create_repo("alice/thing", "model", gated_mode=bad) + assert not routes.openapi.called and not routes.legacy.called diff --git a/tests/test_dataset_category.py b/tests/test_dataset_category.py new file mode 100644 index 0000000..436315c --- /dev/null +++ b/tests/test_dataset_category.py @@ -0,0 +1,198 @@ +"""The dataset zone endpoints, and the settings fields that are not writable. + +Mirrors the model zone rules with two differences worth pinning: the general +dataset category is ``base`` (not ``basic``, as earlier drafts had it), and +``login_required`` appears in a dataset's detail payload but no request schema +accepts it, so it cannot be set through the SDK at all. +""" + +from __future__ import annotations + +from unittest import mock + +import pytest + +from modelscope_hub import HubApi +from modelscope_hub import api as api_module +from modelscope_hub.errors import InvalidParameter, NotSupportedError + + +@pytest.fixture +def api() -> HubApi: + return HubApi(endpoint="https://modelscope.cn", token="ms-test") + + +@pytest.fixture +def request_mock(api: HubApi): + with mock.patch.object(api.openapi, "_request", return_value={"id": "alice/d"}) as m: + yield m + + +# --------------------------------------------------------------------------- +# PUT /category +# --------------------------------------------------------------------------- +def test_setting_a_zone_uses_put(api, request_mock): + api.set_dataset_category( + "alice/d", + dataset_category="science", + primary_subject_area="life-sciences", + secondary_specialization="dna", + ) + + assert request_mock.call_args.args == ("PUT", "/datasets/alice/d/category") + assert request_mock.call_args.kwargs["json_body"] == { + "dataset_category": "science", + "primary_subject_area": "life-sciences", + "secondary_specialization": "dna", + } + + +def test_the_general_category_is_base_not_basic(api, request_mock): + api.set_dataset_category("alice/d", dataset_category="base") + + assert request_mock.call_args.kwargs["json_body"] == {"dataset_category": "base"} + + +def test_basic_is_rejected_and_the_valid_values_are_named(api, request_mock): + with pytest.raises(InvalidParameter, match="'base', 'science'"): + api.set_dataset_category("alice/d", dataset_category="basic") + request_mock.assert_not_called() + + +# --------------------------------------------------------------------------- +# PATCH /category +# --------------------------------------------------------------------------- +def test_updating_a_zone_uses_patch(api, request_mock): + api.update_dataset_category("alice/d", secondary_specialization="medicine") + + assert request_mock.call_args.args == ("PATCH", "/datasets/alice/d/category") + assert request_mock.call_args.kwargs["json_body"] == {"secondary_specialization": "medicine"} + + +def test_the_category_discriminator_is_dropped_with_a_warning(api, request_mock, caplog, monkeypatch): + monkeypatch.setattr(api_module.logger, "handlers", [*api_module.logger.handlers, caplog.handler]) + with caplog.at_level("WARNING", logger="modelscope_hub.api"): + api.update_dataset_category("alice/d", dataset_category="science", cover_image="https://x/y.png") + + assert "dataset_category" not in request_mock.call_args.kwargs["json_body"] + assert "set_dataset_category" in caplog.text + + +def test_an_explicit_none_is_forwarded_to_clear_a_field(api, request_mock): + """Omitting a field leaves it unchanged; ``None`` clears it.""" + api.update_dataset_category("alice/d", cover_image=None) + + assert request_mock.call_args.kwargs["json_body"] == {"cover_image": None} + + +# --------------------------------------------------------------------------- +# PATCH /settings +# --------------------------------------------------------------------------- +def test_dataset_settings_reach_the_settings_endpoint(api, request_mock): + api.update_repo_settings("alice/d", "dataset", description="new", display_name="D") + + assert request_mock.call_args.args == ("PATCH", "/datasets/alice/d/settings") + assert request_mock.call_args.kwargs["json_body"] == {"description": "new", "display_name": "D"} + + +def test_model_settings_reach_the_settings_endpoint(api, request_mock): + api.update_repo_settings("alice/m", "model", task="text-generation") + + assert request_mock.call_args.args == ("PATCH", "/models/alice/m/settings") + assert request_mock.call_args.kwargs["json_body"] == {"task": "text-generation"} + + +@pytest.mark.parametrize("repo_type", ["model", "dataset"]) +@pytest.mark.parametrize("visibility", [None, "private", 1, "1"]) +@pytest.mark.parametrize("switch", [True, False]) +def test_settings_use_historical_switch_and_visibility(api, request_mock, repo_type, visibility, switch): + api.update_repo_settings("alice/x", repo_type, visibility=visibility, gated_mode=switch) + expected = {"visibility": "gated", "gated_mode": "auto"} if switch else {"visibility": "private"} + assert request_mock.call_args.kwargs["json_body"] == expected + + +@pytest.mark.parametrize("mode", ["auto", "manual", ""]) +def test_native_settings_payload_still_belongs_to_openapi(api, request_mock, mode): + payload = {"visibility": "gated", "gated_mode": mode} + api.openapi.update_dataset_settings("alice", "d", payload) + assert request_mock.call_args.kwargs["json_body"] == payload + + +@pytest.mark.parametrize("bad", ["auto", "manual", "", 0, []]) +def test_settings_reject_non_boolean_switches(api, request_mock, bad): + with pytest.raises(InvalidParameter, match="gated_mode must be"): + api.update_repo_settings("alice/d", "dataset", gated_mode=bad) + request_mock.assert_not_called() + + +@pytest.mark.parametrize("name", ["approval_mode", "gated"]) +@pytest.mark.parametrize("value", [None, False, "manual"]) +@pytest.mark.parametrize("repo_type", ["model", "dataset", "studio", "skill"]) +def test_removed_settings_parameters_are_rejected_before_sending(api, request_mock, repo_type, name, value): + with pytest.raises(TypeError, match=name): + api.update_repo_settings("alice/d", repo_type, **{name: value}) + request_mock.assert_not_called() + + +def test_settings_none_is_not_a_new_gating_policy(api, request_mock): + api.update_repo_settings("alice/d", "dataset", gated_mode=None, visibility=None, description="same") + assert request_mock.call_args.kwargs["json_body"] == {"description": "same"} + + +def test_settings_do_not_accept_native_gated_visibility(api, request_mock): + with pytest.raises(ValueError, match="Unknown visibility"): + api.update_repo_settings("alice/d", "dataset", visibility="gated") + request_mock.assert_not_called() + + +def test_integer_visibility_is_translated_for_settings(api, request_mock): + api.update_repo_settings("alice/d", "dataset", visibility=1) + + assert request_mock.call_args.kwargs["json_body"] == {"visibility": "private"} + + +def test_internal_visibility_is_refused_for_settings(api, request_mock): + """Unlike create, there is no legacy settings endpoint to fall back to.""" + with pytest.raises(InvalidParameter, match="not supported"): + api.update_repo_settings("alice/d", "dataset", visibility="internal") + request_mock.assert_not_called() + + +def test_login_required_is_reported_as_unsupported(api, request_mock): + """No request schema accepts it, so silently dropping it would mislead.""" + with pytest.raises(NotSupportedError, match="login_required"): + api.update_repo_settings("alice/d", "dataset", login_required=True) + request_mock.assert_not_called() + + +def test_unsupported_repo_types_still_raise(api, request_mock): + with pytest.raises(NotSupportedError): + api.update_repo_settings("alice/x", "mcp", description="x") + + +# --------------------------------------------------------------------------- +# High-level historical switch, not an approval-policy selector +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("repo_type", ["model", "dataset"]) +def test_false_switch_changes_to_private_without_native_boolean(api, request_mock, repo_type): + api.update_repo_settings("alice/x", repo_type, gated_mode=False) + assert request_mock.call_args.kwargs["json_body"] == {"visibility": "private"} + + +def test_public_visibility_wins_over_only_boolean_switch(api, request_mock, caplog, monkeypatch): + monkeypatch.setattr(api_module.logger, "handlers", [*api_module.logger.handlers, caplog.handler]) + api.update_repo_settings("alice/x", "model", visibility="public", gated_mode=True) + assert request_mock.call_args.kwargs["json_body"] == {"visibility": "public"} + assert "ignored" in caplog.text + + +# --------------------------------------------------------------------------- +# Repo type guards +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("repo_type", ["model", "studio", "skill"]) +def test_the_dataset_methods_reject_other_repo_types(api, request_mock, repo_type): + with pytest.raises(NotSupportedError): + api.set_dataset_category("alice/d", repo_type, dataset_category="base") + with pytest.raises(NotSupportedError): + api.update_dataset_category("alice/d", repo_type, cover_image=None) + request_mock.assert_not_called() diff --git a/tests/test_model_category.py b/tests/test_model_category.py new file mode 100644 index 0000000..4e8a66c --- /dev/null +++ b/tests/test_model_category.py @@ -0,0 +1,261 @@ +"""The model zone endpoints: ``PUT`` to change zone, ``PATCH`` to edit in place. + +The two verbs are not interchangeable, and their payload rules are close to +inverses of each other: + +* ``PUT`` sets the zone. Required fields follow from the *target* zone, and + fields belonging to another zone are rejected. +* ``PATCH`` edits the current zone. The server reads the zone itself and + rejects the discriminator, addresses AIGC versions by numeric id, and refuses + to touch an existing version's weights. + +Checks the SDK can make locally are made locally, so a request certain to fail +names the fix instead of costing a round trip for a generic 400. +""" + +from __future__ import annotations + +from unittest import mock + +import pytest + +from modelscope_hub import HubApi +from modelscope_hub import api as api_module +from modelscope_hub.constants import WEIGHT_FILE_SUFFIXES +from modelscope_hub.errors import InvalidParameter, NotSupportedError + +_AIGC = { + "aigc_type": "LoRA", + "base_model_architecture_type": "Z_IMAGE_TURBO", + "base_model_architecture_subtype": "Z_IMAGE_TURBO", + "version_name": "v1.0", + "weight_files": [{"path_in_repo": "model.safetensors"}], + "sample_images": [{"url": "https://example.com/a.png", "prompt": "a cat"}], +} + + +@pytest.fixture +def api() -> HubApi: + return HubApi(endpoint="https://modelscope.cn", token="ms-test") + + +@pytest.fixture +def request_mock(api: HubApi): + with mock.patch.object(api.openapi, "_request", return_value={"id": "alice/m"}) as m: + yield m + + +# --------------------------------------------------------------------------- +# PUT: verb, path, and the per-zone required fields +# --------------------------------------------------------------------------- +def test_setting_a_zone_uses_put(api, request_mock): + api.set_model_category("alice/m", model_category="base") + + assert request_mock.call_args.args == ("PUT", "/models/alice/m/category") + assert request_mock.call_args.kwargs["json_body"] == {"model_category": "base"} + + +def test_moving_to_aigc_sends_every_required_field(api, request_mock): + api.set_model_category("alice/m", model_category="aigc", **_AIGC) + + body = request_mock.call_args.kwargs["json_body"] + assert body["model_category"] == "aigc" + assert body["version_name"] == "v1.0" + assert body["weight_files"] == [{"path_in_repo": "model.safetensors"}] + assert body["sample_images"] == [{"url": "https://example.com/a.png", "prompt": "a cat"}] + + +def test_moving_to_science_needs_only_the_subject_areas(api, request_mock): + api.set_model_category( + "alice/m", + model_category="science", + primary_subject_area="life-sciences", + secondary_specialization="dna", + ) + + assert request_mock.call_args.kwargs["json_body"] == { + "model_category": "science", + "primary_subject_area": "life-sciences", + "secondary_specialization": "dna", + } + + +@pytest.mark.parametrize("missing", sorted(_AIGC)) +def test_a_missing_aigc_field_is_reported_before_the_request(api, request_mock, missing): + payload = {k: v for k, v in _AIGC.items() if k != missing} + + with pytest.raises(InvalidParameter, match=missing): + api.set_model_category("alice/m", model_category="aigc", **payload) + request_mock.assert_not_called() + + +@pytest.mark.parametrize("empty", [[], None]) +def test_empty_weight_files_and_sample_images_are_rejected(api, request_mock, empty): + for field in ("weight_files", "sample_images"): + with pytest.raises(InvalidParameter, match=field): + api.set_model_category("alice/m", model_category="aigc", **{**_AIGC, field: empty}) + request_mock.assert_not_called() + + +def test_an_unknown_category_names_the_valid_values(api, request_mock): + with pytest.raises(InvalidParameter, match="'base', 'aigc', 'science'"): + api.set_model_category("alice/m", model_category="basic") + request_mock.assert_not_called() + + +def test_an_unknown_aigc_type_names_the_valid_values(api, request_mock): + with pytest.raises(InvalidParameter, match="Checkpoint"): + api.set_model_category("alice/m", model_category="aigc", **{**_AIGC, "aigc_type": "lora"}) + request_mock.assert_not_called() + + +@pytest.mark.parametrize("suffix", [".safetensors", ".CKPT", ".Bin", ".onnx"]) +def test_recognised_weight_suffixes_are_accepted_case_insensitively(api, request_mock, suffix): + api.set_model_category( + "alice/m", + model_category="aigc", + **{**_AIGC, "weight_files": [{"path_in_repo": f"sub/dir/model{suffix}"}]}, + ) + + assert request_mock.called + + +def test_an_unrecognised_weight_suffix_lists_the_supported_ones(api, request_mock): + with pytest.raises(InvalidParameter) as excinfo: + api.set_model_category( + "alice/m", + model_category="aigc", + **{**_AIGC, "weight_files": [{"path_in_repo": "notes.txt"}]}, + ) + + message = str(excinfo.value) + assert "notes.txt" in message + for suffix in WEIGHT_FILE_SUFFIXES: + assert suffix in message + request_mock.assert_not_called() + + +def test_a_weight_file_without_a_path_is_rejected(api, request_mock): + with pytest.raises(InvalidParameter, match="path_in_repo"): + api.set_model_category("alice/m", model_category="aigc", **{**_AIGC, "weight_files": [{}]}) + + +def test_a_sample_image_without_a_url_is_rejected(api, request_mock): + with pytest.raises(InvalidParameter, match="url"): + api.set_model_category( + "alice/m", + model_category="aigc", + **{**_AIGC, "sample_images": [{"prompt": "no url"}]}, + ) + + +def test_the_retired_sample_image_text_key_is_rejected(api, request_mock): + """The schema takes ``prompt``/``negative_prompt``; ``text`` never existed.""" + with pytest.raises(InvalidParameter, match="prompt"): + api.set_model_category( + "alice/m", + model_category="aigc", + **{**_AIGC, "sample_images": [{"url": "https://example.com/a.png", "text": "cat"}]}, + ) + + +def test_an_overlong_version_name_is_rejected(api, request_mock): + with pytest.raises(InvalidParameter, match="version_name"): + api.set_model_category("alice/m", model_category="aigc", **{**_AIGC, "version_name": "v" * 21}) + + +# --------------------------------------------------------------------------- +# PATCH: verb, path, and the inverted payload rules +# --------------------------------------------------------------------------- +def test_updating_a_zone_uses_patch(api, request_mock): + api.update_model_category("alice/m", primary_subject_area="life-sciences") + + assert request_mock.call_args.args == ("PATCH", "/models/alice/m/category") + assert request_mock.call_args.kwargs["json_body"] == {"primary_subject_area": "life-sciences"} + + +def test_the_category_discriminator_is_dropped_with_a_warning(api, request_mock, caplog, monkeypatch): + """Earlier drafts required the field; the endpoint now 400s if it is present.""" + monkeypatch.setattr(api_module.logger, "handlers", [*api_module.logger.handlers, caplog.handler]) + with caplog.at_level("WARNING", logger="modelscope_hub.api"): + api.update_model_category("alice/m", model_category="aigc", trigger_words=["x"]) + + assert "model_category" not in request_mock.call_args.kwargs["json_body"] + assert "set_model_category" in caplog.text + + +@pytest.mark.parametrize("field", ["weight_files", "base_model_id"]) +def test_fields_only_put_can_change_are_refused(api, request_mock, field): + with pytest.raises(InvalidParameter, match="set_model_category"): + api.update_model_category("alice/m", **{field: "whatever"}) + request_mock.assert_not_called() + + +def test_version_name_is_a_resolution_hint_not_a_wire_field(api): + """The endpoint rejects ``version_name``; the SDK turns it into a lookup.""" + detail = {"versions": [{"version_name": "v1.0", "version_id": 42}]} + + with mock.patch.object(api.openapi, "_request", side_effect=[detail, {"id": "alice/m"}]) as request_mock: + api.update_model_category("alice/m", version_name="v1.0", trigger_words=["x"]) + + body = request_mock.call_args_list[1].kwargs["json_body"] + assert "version_name" not in body + assert body["version_id"] == 42 + + +def test_an_explicit_version_id_is_forwarded(api, request_mock): + api.update_model_category("alice/m", version_id=1070760, trigger_words=["anime"]) + + assert request_mock.call_args.kwargs["json_body"]["version_id"] == 1070760 + + +def test_a_science_update_sends_no_version_id(api, request_mock): + """The endpoint rejects ``version_id`` outside the AIGC zone.""" + api.update_model_category("alice/m", cover_image=None) + + assert "version_id" not in request_mock.call_args.kwargs["json_body"] + + +# --------------------------------------------------------------------------- +# version_name -> version_id resolution +# --------------------------------------------------------------------------- +def test_a_version_name_is_resolved_to_its_numeric_id(api): + detail = {"model_category": "aigc", "versions": [{"version_name": "v1.0", "version_id": 42}]} + + with mock.patch.object(api.openapi, "_request", side_effect=[detail, {"id": "alice/m"}]) as request_mock: + api.update_model_category("alice/m", version_name="v1.0", trigger_words=["anime"]) + + detail_call, update_call = request_mock.call_args_list + assert detail_call.args == ("GET", "/models/alice/m") + body = update_call.kwargs["json_body"] + assert body["version_id"] == 42 + assert "version_name" not in body + + +def test_an_unknown_version_name_lists_what_is_available(api): + detail = {"versions": [{"version_name": "v1.0", "version_id": 42}]} + + with mock.patch.object(api.openapi, "_request", return_value=detail): + with pytest.raises(InvalidParameter) as excinfo: + api.update_model_category("alice/m", version_name="v9.9", trigger_words=["x"]) + + assert "'v1.0'" in str(excinfo.value) + + +def test_an_explicit_version_id_skips_the_lookup(api, request_mock): + api.update_model_category("alice/m", version_id=7, version_name="v1.0", trigger_words=["x"]) + + request_mock.assert_called_once() + assert request_mock.call_args.kwargs["json_body"]["version_id"] == 7 + + +# --------------------------------------------------------------------------- +# Repo type guards +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("repo_type", ["dataset", "studio", "skill"]) +def test_the_model_methods_reject_other_repo_types(api, request_mock, repo_type): + with pytest.raises(NotSupportedError): + api.set_model_category("alice/m", repo_type, model_category="base") + with pytest.raises(NotSupportedError): + api.update_model_category("alice/m", repo_type, trigger_words=["x"]) + request_mock.assert_not_called() diff --git a/tests/test_model_detail_fields.py b/tests/test_model_detail_fields.py new file mode 100644 index 0000000..e4e0d1a --- /dev/null +++ b/tests/test_model_detail_fields.py @@ -0,0 +1,235 @@ +"""``get_model`` / ``get_dataset`` return the detail payload, not a ``RepoInfo``. + +The zone fields -- AIGC architecture and ``versions``, or the science subject +areas -- have no place on the ``RepoInfo`` dataclass, so ``get_repo`` drops +them. These methods hand back the server's payload as-is instead. + +Which keys are present depends on the category: the fields of the zones a +repository is *not* in are absent rather than null, so callers must use +``.get()`` and must not assume a fixed shape. +""" + +from __future__ import annotations + +from unittest import mock + +import pytest + +from modelscope_hub import HubApi +from modelscope_hub.compat import LegacyHubApi +from modelscope_hub.constants import RepoType + +_BASE_MODEL = { + "id": "alice/plain", + "owner": "alice", + "repo_name": "plain", + "model_category": "base", + "visibility": "public", + "downloads": 10, + "likes": 2, +} + +_AIGC_MODEL = { + "id": "alice/lora", + "owner": "alice", + "repo_name": "lora", + "model_category": "aigc", + "visibility": "public", + "aigc_type": "LoRA", + "base_model_architecture_type": "Z_IMAGE_TURBO", + "base_model_architecture_subtype": "Z_IMAGE_TURBO", + "content_categories": {"style": "ILLUSTRATION"}, + "versions": [ + { + "version_id": 1070760, + "version_name": "v1.0", + "weight_files": [{"path_in_repo": "model.safetensors", "size": 42, "sha": "abc"}], + "sample_images": [{"url": "https://example.com/a.png", "prompt": "a cat"}], + "trigger_words": ["anime"], + "created_at": "2026-06-22T10:00:00Z", + } + ], +} + +_SCIENCE_MODEL = { + "id": "alice/rna", + "owner": "alice", + "repo_name": "rna", + "model_category": "science", + "primary_subject_area": "life-sciences", + "secondary_specialization": "dna", + "cover_image": "https://example.com/cover.png", +} + +_SCIENCE_DATASET = { + "id": "alice/rna-seq", + "owner": "alice", + "repo_name": "rna-seq", + "dataset_category": "science", + "login_required": True, + "primary_subject_area": "life-sciences", + "secondary_specialization": "dna", +} + + +@pytest.fixture +def api() -> HubApi: + return HubApi(endpoint="https://modelscope.cn", token="ms-test") + + +# --------------------------------------------------------------------------- +# Routing +# --------------------------------------------------------------------------- +def test_get_model_reads_the_model_detail_endpoint(api): + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_MODEL) as request_mock: + detail = api.get_model("alice/lora") + + assert request_mock.call_args.args == ("GET", "/models/alice/lora") + assert detail == _AIGC_MODEL + + +def test_get_dataset_reads_the_dataset_detail_endpoint(api): + with mock.patch.object(api.openapi, "_request", return_value=_SCIENCE_DATASET) as request_mock: + detail = api.get_dataset("alice/rna-seq") + + assert request_mock.call_args.args == ("GET", "/datasets/alice/rna-seq") + assert detail == _SCIENCE_DATASET + + +@pytest.mark.parametrize(("alias", "canonical"), [("model_info", "get_model"), ("dataset_info", "get_dataset")]) +def test_the_info_aliases_match_their_canonical_methods(api, alias, canonical): + with mock.patch.object(api.openapi, "_request", return_value=_BASE_MODEL) as request_mock: + from_alias = getattr(api, alias)("alice/plain") + from_canonical = getattr(api, canonical)("alice/plain") + + assert from_alias == from_canonical + assert request_mock.call_args_list[0].args == request_mock.call_args_list[1].args + + +# --------------------------------------------------------------------------- +# Zone fields survive, and are category-dependent +# --------------------------------------------------------------------------- +def test_aigc_versions_survive_with_their_numeric_ids(api): + """``update_model_category`` needs ``version_id``, so it must not be lost.""" + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_MODEL): + detail = api.get_model("alice/lora") + + version = detail["versions"][0] + assert version["version_id"] == 1070760 + assert version["weight_files"][0]["sha"] == "abc" + assert version["sample_images"][0]["prompt"] == "a cat" + + +def test_a_base_model_carries_no_zone_fields(api): + with mock.patch.object(api.openapi, "_request", return_value=_BASE_MODEL): + detail = api.get_model("alice/plain") + + assert detail["model_category"] == "base" + for absent in ("aigc_type", "versions", "primary_subject_area", "cover_image"): + assert absent not in detail + + +def test_a_science_model_carries_subject_areas_and_no_versions(api): + with mock.patch.object(api.openapi, "_request", return_value=_SCIENCE_MODEL): + detail = api.get_model("alice/rna") + + assert detail["primary_subject_area"] == "life-sciences" + assert detail["cover_image"] == "https://example.com/cover.png" + assert "versions" not in detail + assert "aigc_type" not in detail + + +def test_an_aigc_model_carries_no_cover_image(api): + """Cover images belong to the science zone only.""" + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_MODEL): + detail = api.get_model("alice/lora") + + assert "cover_image" not in detail + + +def test_login_required_is_readable_even_though_it_is_not_writable(api): + with mock.patch.object(api.openapi, "_request", return_value=_SCIENCE_DATASET): + detail = api.get_dataset("alice/rna-seq") + + assert detail["login_required"] is True + + +def test_a_non_mapping_payload_degrades_to_an_empty_dict(api): + with mock.patch.object(api.openapi, "_request", return_value=None): + assert api.get_model("alice/plain") == {} + assert api.get_dataset("alice/plain") == {} + + +# --------------------------------------------------------------------------- +# RepoInfo keeps the category, without gaining the zone payload +# --------------------------------------------------------------------------- +def test_get_repo_reports_the_category_but_not_the_zone_payload(api): + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_MODEL): + info = api.get_repo("alice/lora", "model") + + assert info.model_category == "aigc" + assert info.repo_id == "alice/lora" + # ``versions`` has no dataclass field: use get_model() for it. + assert not hasattr(info, "versions") + + +def test_a_dataset_repo_info_reports_its_category(api): + with mock.patch.object(api.openapi, "_request", return_value=_SCIENCE_DATASET): + info = api.get_repo("alice/rna-seq", "dataset") + + assert info.dataset_category == "science" + assert info.login_required is True + + +def test_the_new_fields_are_omitted_from_to_dict_when_absent(api): + with mock.patch.object(api.openapi, "_request", return_value={"id": "alice/x", "private": False}): + info = api.get_repo("alice/x", "studio") + + as_dict = info.to_dict() + for optional in ("model_category", "dataset_category", "gated_mode"): + assert optional not in as_dict + + +# --------------------------------------------------------------------------- +# Compat surface keeps both shapes available +# --------------------------------------------------------------------------- +def test_compat_get_model_keeps_its_legacy_pascalcase_shape(api): + compat = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + + with mock.patch.object(compat._api.openapi, "_request", return_value=_AIGC_MODEL): + out = compat.get_model("alice/lora") + + # Historical contract: a flattened RepoInfo with PascalCase keys. + assert out["Id"] == "alice/lora" + assert "model_category" in out # carried through as a RepoInfo field + + +def test_compat_get_model_detail_returns_the_raw_payload(api): + compat = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + + with mock.patch.object(compat._api.openapi, "_request", return_value=_AIGC_MODEL): + out = compat.get_model_detail("alice/lora") + + assert out == _AIGC_MODEL + assert out["versions"][0]["version_id"] == 1070760 + + +def test_compat_get_dataset_detail_returns_the_raw_payload(api): + compat = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + + with mock.patch.object(compat._api.openapi, "_request", return_value=_SCIENCE_DATASET): + out = compat.get_dataset_detail("alice/rna-seq") + + assert out == _SCIENCE_DATASET + + +def test_compat_get_dataset_still_returns_a_repo_info(): + """Deprecated but unchanged: it hands back the dataclass, not a dict.""" + compat = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + + with mock.patch.object(compat._api.openapi, "_request", return_value=_SCIENCE_DATASET): + with pytest.deprecated_call(): + out = compat.get_dataset("alice/rna-seq") + + assert out.repo_id == "alice/rna-seq" + assert out.repo_type is RepoType.DATASET diff --git a/tests/test_model_options.py b/tests/test_model_options.py new file mode 100644 index 0000000..f7cf625 --- /dev/null +++ b/tests/test_model_options.py @@ -0,0 +1,207 @@ +"""The enum option endpoints: anonymous, cached, and ``{type, id, label}``. + +These four endpoints publish product configuration rather than account data, so +they take no credentials and must stay reachable without a token. Their values +change on a release cadence, so repeated reads are served from a short-lived +cache -- otherwise setting a category would re-fetch the whole enum tree on +every call. +""" + +from __future__ import annotations + +import importlib +from unittest import mock + +import pytest + +from modelscope_hub import HubApi + +_AIGC_OPTIONS = { + "aigc_types": [{"type": "aigc_type", "id": "LoRA", "label": "LoRA"}], + "base_architectures": [ + { + "type": "base_architecture", + "id": "Z_IMAGE_TURBO", + "label": "Z-Image Turbo", + "subtypes": [{"type": "base_architecture_subtype", "id": "Z_IMAGE_TURBO", "label": "Z-Image Turbo"}], + } + ], + "content_styles": [{"type": "content_style", "id": "ILLUSTRATION", "label": "插画"}], + "content_functions": [{"type": "content_function", "id": "TEXT_TO_IMAGE", "label": "文生图"}], + "content_elements": [{"type": "content_element", "id": "PERSON", "label": "人物"}], +} + +_SCIENCE_OPTIONS = { + "subject_areas": [ + { + "type": "subject_area", + "id": "life-sciences", + "label": "生命科学", + "specializations": [{"type": "specialization", "id": "dna", "label": "基因组学"}], + } + ] +} + +_MODEL_OPTIONS = { + "tasks": [{"type": "task", "id": "text-generation", "label": "Text Generation"}], + "license": [{"type": "license", "id": "apache-2.0", "label": "Apache License 2.0"}], +} + +# (HubApi method, request path, payload) +_ENDPOINTS = [ + ("list_aigc_model_options", "/model/aigc/options", _AIGC_OPTIONS), + ("list_science_model_options", "/model/science/options", _SCIENCE_OPTIONS), + ("list_science_dataset_options", "/dataset/science/options", _SCIENCE_OPTIONS), + ("list_model_options", "/models/options", _MODEL_OPTIONS), +] + + +@pytest.fixture +def api() -> HubApi: + return HubApi(endpoint="https://modelscope.cn", token="ms-test") + + +# --------------------------------------------------------------------------- +# Routing and anonymity +# --------------------------------------------------------------------------- +@pytest.mark.parametrize(("method", "path", "payload"), _ENDPOINTS) +def test_each_endpoint_is_reached_without_requiring_a_token(api, method, path, payload): + with mock.patch.object(api.openapi, "_request", return_value=payload) as request_mock: + result = getattr(api, method)() + + assert request_mock.call_args.args == ("GET", path) + assert request_mock.call_args.kwargs["require_token"] is False + assert result == payload + + +@pytest.mark.parametrize(("method", "path", "payload"), _ENDPOINTS) +def test_the_endpoints_work_with_no_credentials_at_all(method, path, payload): + anonymous = HubApi(endpoint="https://modelscope.cn", token=None) + + with mock.patch.object(anonymous.openapi, "_request", return_value=payload) as request_mock: + getattr(anonymous, method)() + + assert request_mock.call_args.kwargs["require_token"] is False + + +@pytest.mark.parametrize(("method", "_path", "_payload"), _ENDPOINTS) +def test_a_non_mapping_payload_degrades_to_an_empty_dict(api, method, _path, _payload): + with mock.patch.object(api.openapi, "_request", return_value=None): + assert getattr(api, method)() == {} + + +# --------------------------------------------------------------------------- +# Option item shape +# --------------------------------------------------------------------------- +def test_options_are_submitted_by_id_and_displayed_by_label(api): + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_OPTIONS): + options = api.list_aigc_model_options() + + architecture = options["base_architectures"][0] + for key in ("type", "id", "label"): + assert key in architecture + assert "value" not in architecture # an earlier draft's spelling + assert architecture["subtypes"][0]["id"] == "Z_IMAGE_TURBO" + + +def test_the_science_tree_cascades_from_subject_area_to_specialization(api): + with mock.patch.object(api.openapi, "_request", return_value=_SCIENCE_OPTIONS): + tree = api.list_science_model_options() + + area = tree["subject_areas"][0] + assert area["id"] == "life-sciences" + assert [s["id"] for s in area["specializations"]] == ["dna"] + + +def test_model_options_publish_tasks_and_licenses(api): + with mock.patch.object(api.openapi, "_request", return_value=_MODEL_OPTIONS): + options = api.list_model_options() + + assert [t["id"] for t in options["tasks"]] == ["text-generation"] + assert [lic["id"] for lic in options["license"]] == ["apache-2.0"] + + +# --------------------------------------------------------------------------- +# Caching +# --------------------------------------------------------------------------- +def test_a_repeated_read_is_served_from_the_cache(api): + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_OPTIONS) as request_mock: + first = api.list_aigc_model_options() + second = api.list_aigc_model_options() + + request_mock.assert_called_once() + assert first == second + + +def test_each_endpoint_is_cached_separately(api): + with mock.patch.object(api.openapi, "_request", side_effect=[_AIGC_OPTIONS, _SCIENCE_OPTIONS]) as request_mock: + api.list_aigc_model_options() + api.list_science_model_options() + + assert [call.args[1] for call in request_mock.call_args_list] == [ + "/model/aigc/options", + "/model/science/options", + ] + + +def test_clearing_the_cache_forces_a_refetch(api): + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_OPTIONS) as request_mock: + api.list_aigc_model_options() + api.openapi.clear_options_cache() + api.list_aigc_model_options() + + assert request_mock.call_count == 2 + + +def test_two_clients_do_not_share_a_cache(api): + other = HubApi(endpoint="https://modelscope.cn", token="ms-test") + + with ( + mock.patch.object(api.openapi, "_request", return_value=_AIGC_OPTIONS) as first_mock, + mock.patch.object(other.openapi, "_request", return_value=_AIGC_OPTIONS) as second_mock, + ): + api.list_aigc_model_options() + other.list_aigc_model_options() + + first_mock.assert_called_once() + second_mock.assert_called_once() + + +def test_a_zero_ttl_disables_caching(monkeypatch): + """``MODELSCOPE_OPTIONS_CACHE_TTL=0`` is the documented opt-out.""" + monkeypatch.setenv("MODELSCOPE_OPTIONS_CACHE_TTL", "0") + import modelscope_hub.constants as constants_module + + importlib.reload(constants_module) + assert constants_module.OPTIONS_CACHE_TTL_SECONDS == 0 + + import modelscope_hub._openapi as openapi_module + + importlib.reload(openapi_module) + try: + client = openapi_module.OpenAPIClient() + with mock.patch.object(client, "_request", return_value=_AIGC_OPTIONS) as request_mock: + client.list_aigc_model_options() + client.list_aigc_model_options() + assert request_mock.call_count == 2 + finally: + # Restore the real modules so later tests see the default TTL. + monkeypatch.delenv("MODELSCOPE_OPTIONS_CACHE_TTL", raising=False) + importlib.reload(constants_module) + importlib.reload(openapi_module) + importlib.reload(importlib.import_module("modelscope_hub.api")) + importlib.reload(importlib.import_module("modelscope_hub")) + + +def test_an_expired_entry_is_refetched(api, monkeypatch): + import modelscope_hub._openapi as openapi_module + + monkeypatch.setattr(openapi_module, "OPTIONS_CACHE_TTL_SECONDS", 300) + clock = iter([0.0, 0.0, 1000.0, 1000.0]) + monkeypatch.setattr(openapi_module.time, "monotonic", lambda: next(clock)) + + with mock.patch.object(api.openapi, "_request", return_value=_AIGC_OPTIONS) as request_mock: + api.list_aigc_model_options() + api.list_aigc_model_options() + + assert request_mock.call_count == 2 diff --git a/tests/test_openapi_coverage.py b/tests/test_openapi_coverage.py index 344f87b..096d23d 100644 --- a/tests/test_openapi_coverage.py +++ b/tests/test_openapi_coverage.py @@ -24,20 +24,20 @@ _HTTP_METHODS = frozenset({"get", "post", "put", "delete", "patch", "head", "options"}) # Tags the SDK implements end to end. -_COVERED_TAGS = frozenset({"Agent-IDP", "MCP", "Studios"}) +_COVERED_TAGS = frozenset({"Agent-IDP", "Datasets", "MCP", "Models", "Studios"}) # Tags not implemented yet. Remove an entry as its tag lands; until then the # guard would otherwise fail on operations nobody has promised. _DEFERRED_TAGS = frozenset( { "User", - "Models", - "Datasets", "Skills", "Files", "Collections", "Galleries", "Magicube", + "AgentHub", + "Agent Playground", } ) diff --git a/tests/test_openapi_delete_files.py b/tests/test_openapi_delete_files.py new file mode 100644 index 0000000..a91b0c1 --- /dev/null +++ b/tests/test_openapi_delete_files.py @@ -0,0 +1,160 @@ +"""The OpenAPI delete-files endpoint, and why ``delete_files`` does not use it. + +The endpoint is strictly narrower than the commit-based deletion the SDK +already performs: it caps at 100 paths, takes no directories or glob patterns, +accepts only a branch, and is all-or-nothing. The commit path has none of those +limits, so :meth:`HubApi.delete_files` keeps using it; routing it here would +turn working calls into failures. + +The endpoint is still exposed on the OpenAPI client for callers that want its +``commit_sha`` and its strict guarantee. The regression guard below is the +point of this file: ``HubApi.delete_files`` must stay on the commit path. +""" + +from __future__ import annotations + +from unittest import mock + +import pytest + +from modelscope_hub import HubApi +from modelscope_hub.constants import OPENAPI_DELETE_FILES_MAX_PATHS +from modelscope_hub.errors import InvalidParameter + +_RESULT = { + "deleted_files": ["a.txt", "b.txt"], + "failed_files": [], + "total_files": 2, + "commit_sha": "a1b2c3", +} + + +@pytest.fixture +def api() -> HubApi: + return HubApi(endpoint="https://modelscope.cn", token="ms-test") + + +# --------------------------------------------------------------------------- +# The endpoint itself +# --------------------------------------------------------------------------- +@pytest.mark.parametrize( + ("method", "path"), + [ + ("delete_model_files", "/models/alice/m/files"), + ("delete_dataset_files", "/datasets/alice/m/files"), + ], +) +def test_the_request_uses_delete_with_a_json_body(api, method, path): + with mock.patch.object(api.openapi, "_request", return_value=_RESULT) as request_mock: + result = getattr(api.openapi, method)("alice", "m", {"file_paths": ["a.txt", "b.txt"]}) + + assert request_mock.call_args.args == ("DELETE", path) + assert request_mock.call_args.kwargs["json_body"] == {"file_paths": ["a.txt", "b.txt"]} + assert result["commit_sha"] == "a1b2c3" + + +def test_revision_and_commit_message_are_forwarded(api): + with mock.patch.object(api.openapi, "_request", return_value=_RESULT) as request_mock: + api.openapi.delete_model_files( + "alice", + "m", + {"file_paths": ["a.txt"], "revision": "dev", "commit_message": "tidy up"}, + ) + + body = request_mock.call_args.kwargs["json_body"] + assert body["revision"] == "dev" + assert body["commit_message"] == "tidy up" + + +def test_exactly_the_maximum_number_of_paths_is_accepted(api): + paths = [f"f{i}.txt" for i in range(OPENAPI_DELETE_FILES_MAX_PATHS)] + + with mock.patch.object(api.openapi, "_request", return_value=_RESULT) as request_mock: + api.openapi.delete_model_files("alice", "m", {"file_paths": paths}) + + assert len(request_mock.call_args.kwargs["json_body"]["file_paths"]) == OPENAPI_DELETE_FILES_MAX_PATHS + + +def test_too_many_paths_are_refused_before_the_request(api): + paths = [f"f{i}.txt" for i in range(OPENAPI_DELETE_FILES_MAX_PATHS + 1)] + + with mock.patch.object(api.openapi, "_request") as request_mock: + with pytest.raises(InvalidParameter, match="at most 100"): + api.openapi.delete_model_files("alice", "m", {"file_paths": paths}) + request_mock.assert_not_called() + + +@pytest.mark.parametrize("paths", [[], None, "a.txt"]) +def test_an_empty_or_non_list_path_set_is_refused(api, paths): + with mock.patch.object(api.openapi, "_request") as request_mock: + with pytest.raises(InvalidParameter, match="non-empty list"): + api.openapi.delete_dataset_files("alice", "m", {"file_paths": paths}) + request_mock.assert_not_called() + + +def test_duplicate_paths_are_refused(api): + with mock.patch.object(api.openapi, "_request") as request_mock: + with pytest.raises(InvalidParameter, match="duplicate"): + api.openapi.delete_model_files("alice", "m", {"file_paths": ["a.txt", "a.txt"]}) + request_mock.assert_not_called() + + +# --------------------------------------------------------------------------- +# Regression guard: HubApi.delete_files must not move +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("repo_type", ["model", "dataset"]) +def test_delete_files_still_commits_instead_of_calling_the_endpoint(api, repo_type): + with ( + mock.patch.object(api.uploader, "delete_files", return_value=_RESULT) as uploader_delete, + mock.patch.object(api.openapi, "_request") as openapi_request, + ): + api.delete_files("alice/m", repo_type, ["a.txt", "b.txt"]) + + openapi_request.assert_not_called() + uploader_delete.assert_called_once() + assert uploader_delete.call_args.kwargs["file_paths"] == ["a.txt", "b.txt"] + + +def test_delete_files_has_no_hundred_path_ceiling(api): + """The commit path splits on the server's own action limit, far above 100.""" + paths = [f"f{i}.txt" for i in range(250)] + + with ( + mock.patch.object(api.uploader, "delete_files", return_value=_RESULT) as uploader_delete, + mock.patch.object(api.openapi, "_request") as openapi_request, + ): + api.delete_files("alice/m", "model", paths) + + openapi_request.assert_not_called() + assert len(uploader_delete.call_args.kwargs["file_paths"]) == 250 + + +def test_delete_files_still_resolves_glob_patterns(api): + """The endpoint takes no patterns; the commit path resolves them remotely.""" + remote = [ + mock.Mock(path="keep.bin", type="blob"), + mock.Mock(path="drop/a.json", type="blob"), + mock.Mock(path="drop/b.json", type="blob"), + ] + + with ( + mock.patch.object(api, "list_repo_files", return_value=remote), + mock.patch.object(api.uploader, "delete_files", return_value=_RESULT) as uploader_delete, + mock.patch.object(api.openapi, "_request") as openapi_request, + ): + api.delete_files("alice/m", "model", delete_patterns="*.json") + + openapi_request.assert_not_called() + assert uploader_delete.call_args.kwargs["file_paths"] == ["drop/a.json", "drop/b.json"] + + +def test_delete_files_accepts_a_tag_revision(api): + """``revision`` is forwarded as given; the endpoint would reject a tag.""" + with ( + mock.patch.object(api.uploader, "delete_files", return_value=_RESULT) as uploader_delete, + mock.patch.object(api.openapi, "_request") as openapi_request, + ): + api.delete_files("alice/m", "model", ["a.txt"], revision="v1.0") + + openapi_request.assert_not_called() + assert uploader_delete.call_args.kwargs["revision"] == "v1.0" diff --git a/tests/test_repo_info_visibility.py b/tests/test_repo_info_visibility.py new file mode 100644 index 0000000..920d5a2 --- /dev/null +++ b/tests/test_repo_info_visibility.py @@ -0,0 +1,121 @@ +"""``RepoInfo.visibility`` must stay a :class:`Visibility` integer. + +The model and dataset detail payloads gained ``visibility: "gated"``, a third +string state the 1/3/5 integer encoding cannot express. Letting it fall through +as a raw string would break ``.label``, ``int()`` and equality against the enum +-- and the compat dict, which the legacy SDK reads as an integer. + +A gated repository maps to PRIVATE: that is what it is on the legacy wire +(``Visibility=PRIVATE`` plus ``ProtectedMode=1``), and the approval mode travels +separately in ``gated`` / ``gated_mode``. The server reports ``private: false`` +for gated repos, so without that mapping they would read back as PUBLIC. +""" + +from __future__ import annotations + +from unittest import mock + +import pytest + +from modelscope_hub import HubApi +from modelscope_hub.compat import LegacyHubApi +from modelscope_hub.constants import RepoType, Visibility + + +def _info(payload: dict, repo_type: RepoType = RepoType.MODEL): + return HubApi._repo_info_from_payload(payload, repo_type, owner_hint="alice", name_hint="thing") + + +# --------------------------------------------------------------------------- +# String visibility (current contract) +# --------------------------------------------------------------------------- +@pytest.mark.parametrize( + ("payload", "expected"), + [ + ({"visibility": "public", "private": False, "gated": False}, Visibility.PUBLIC), + ({"visibility": "private", "private": True, "gated": False}, Visibility.PRIVATE), + # A gated repo: the server says ``private: false`` yet it is not public. + ({"visibility": "gated", "private": False, "gated": True}, Visibility.PRIVATE), + ], +) +def test_string_visibility_maps_to_the_integer_enum(payload, expected): + info = _info(payload) + + assert info.visibility == expected + assert isinstance(info.visibility, Visibility) + + +def test_gated_visibility_implies_the_gated_flag(): + """``gated`` may be absent from a summary payload; visibility still implies it.""" + info = _info({"visibility": "gated"}) + + assert info.visibility == Visibility.PRIVATE + assert info.gated is True + + +def test_an_explicit_gated_flag_is_not_overwritten(): + info = _info({"visibility": "gated", "gated": True, "gated_mode": "manual"}) + + assert info.gated is True + assert info.gated_mode == "manual" + + +# --------------------------------------------------------------------------- +# Older payloads with no visibility field +# --------------------------------------------------------------------------- +@pytest.mark.parametrize( + ("payload", "expected"), + [ + ({"private": True}, Visibility.PRIVATE), + ({"private": False}, Visibility.PUBLIC), + # ``gated`` is checked first: a gated repo reports ``private: false``, + # so trusting that flag would report it as public. + ({"private": False, "gated": True}, Visibility.PRIVATE), + ({"gated": True}, Visibility.PRIVATE), + ], +) +def test_boolean_payloads_still_resolve(payload, expected): + info = _info(payload) + + assert info.visibility == expected + assert isinstance(info.visibility, Visibility) + + +def test_an_integer_visibility_is_preserved(): + assert _info({"Visibility": int(Visibility.INTERNAL)}).visibility == Visibility.INTERNAL + + +# --------------------------------------------------------------------------- +# Studio visibility must not regress +# --------------------------------------------------------------------------- +def test_studio_protected_is_preserved_verbatim(): + """``protected`` has no integer equivalent, so it is kept as the string.""" + info = _info({"visibility": "protected"}, RepoType.STUDIO) + + assert info.visibility == "protected" + + +# --------------------------------------------------------------------------- +# Compat surface +# --------------------------------------------------------------------------- +@pytest.mark.parametrize("visibility", ["public", "private", "gated"]) +def test_the_compat_dict_reports_visibility_as_an_integer(visibility): + """The legacy SDK reads ``Visibility`` as an int, never an enum or string.""" + compat = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + payload = {"id": "alice/thing", "visibility": visibility, "private": False} + + with mock.patch.object(compat._api.openapi, "_request", return_value=payload): + out = compat.get_model("alice/thing") + + assert isinstance(out["Visibility"], int) + assert out["Visibility"] in {int(v) for v in Visibility} + + +def test_a_gated_repo_is_not_reported_as_public_through_compat(): + compat = LegacyHubApi(endpoint="https://modelscope.cn", token="ms-test") + payload = {"id": "alice/thing", "visibility": "gated", "private": False, "gated": True} + + with mock.patch.object(compat._api.openapi, "_request", return_value=payload): + out = compat.get_model("alice/thing") + + assert out["Visibility"] == int(Visibility.PRIVATE) diff --git a/tests/test_user_agent.py b/tests/test_user_agent.py index 0e01658..367bae1 100644 --- a/tests/test_user_agent.py +++ b/tests/test_user_agent.py @@ -329,3 +329,16 @@ def fake_download(self, *args, **kwargs): else: assert download("alice/m", **kwargs) == "local-path" assert len(seen) == 1 + + +@pytest.mark.parametrize("switch", [True, False]) +@responses.activate +def test_compat_historical_switch_uses_prepared_request_marker(switch): + """Per-call credentials preserve the SDK/compat UA on historical creation.""" + responses.add(responses.POST, f"{CN}/api/v1/models", json={"Data": {"Id": "alice/m"}}, status=201) + api = LegacyHubApi(endpoint=CN, token=TOKEN) + api.create_repo("alice/m", visibility=1, gated_mode=switch, token=TOKEN, endpoint=CN) + prepared = responses.calls[0].request + assert isinstance(prepared, requests.PreparedRequest) + assert "invoked_by/compat" in _assert_sdk(prepared.headers["User-Agent"]) + assert prepared.headers["Authorization"] == f"Bearer {TOKEN}"