diff --git a/client/starwhale/api/_impl/job/handler.py b/client/starwhale/api/_impl/job/handler.py index ac63563077..1f10d187b5 100644 --- a/client/starwhale/api/_impl/job/handler.py +++ b/client/starwhale/api/_impl/job/handler.py @@ -408,7 +408,7 @@ def generate_jobs_yaml( yaml_path, yaml.safe_dump( { - name: [h.dict() for h in handlers] + name: [h.to_dict() for h in handlers] for name, handlers in expanded_handlers.items() }, default_flow_style=False, diff --git a/client/starwhale/api/_impl/service/service.py b/client/starwhale/api/_impl/service/service.py index f0df70efd7..98ce361b3d 100644 --- a/client/starwhale/api/_impl/service/service.py +++ b/client/starwhale/api/_impl/service/service.py @@ -5,7 +5,7 @@ import typing as t import functools -from .types.types import ComponentSpec +from starwhale.base.client.models.models import ApiSpec, ServiceSpec if sys.version_info >= (3, 9): from importlib.resources import files @@ -27,19 +27,6 @@ ) -class ApiSpec(SwBaseModel): - uri: str - inference_type: str - components_hint: t.List[ComponentSpec] = Field(default_factory=list) - - -class ServiceSpec(SwBaseModel): - title: t.Optional[str] = None - description: t.Optional[str] = None - version: str - apis: t.List[ApiSpec] - - class Api(SwBaseModel): func: t.Callable uri: str @@ -68,7 +55,7 @@ def to_spec(self) -> ApiSpec | None: return ApiSpec( uri=self.uri, inference_type=self.inference_type.name, - components_hint=self.inference_type.components_spec(), + components=self.inference_type.components_spec(), ) diff --git a/client/starwhale/api/_impl/service/types/llm.py b/client/starwhale/api/_impl/service/types/llm.py index d3cdc46702..7b3a716ffc 100644 --- a/client/starwhale/api/_impl/service/types/llm.py +++ b/client/starwhale/api/_impl/service/types/llm.py @@ -1,11 +1,13 @@ from __future__ import annotations import inspect -from typing import Any, Set, List, Callable, Optional +from typing import Any, Set, Dict, List, Callable, Optional from pydantic import BaseModel from pydantic.dataclasses import dataclass +from starwhale.base.client.models.models import ComponentSpecValueType + from .types import ServiceType, ComponentSpec @@ -28,13 +30,13 @@ class LLMChat(ServiceType): name = "llm_chat" # TODO use pydantic model annotations generated arg_types - arg_types = { - "user_input": str, - "history": list, # list of Message - "top_k": int, - "top_p": float, - "temperature": float, - "max_new_tokens": int, + arg_types: Dict[str, ComponentSpecValueType] = { + "user_input": ComponentSpecValueType.string, + "history": ComponentSpecValueType.list, # list of Message + "top_k": ComponentSpecValueType.int, + "top_p": ComponentSpecValueType.float, + "temperature": ComponentSpecValueType.float, + "max_new_tokens": ComponentSpecValueType.int, } def __init__(self, args: Set | None = None) -> None: @@ -50,7 +52,7 @@ def __init__(self, args: Set | None = None) -> None: def components_spec(self) -> List[ComponentSpec]: return [ - ComponentSpec(name=arg, type=self.arg_types[arg].__name__) + ComponentSpec(name=arg, component_spec_value_type=self.arg_types[arg]) for arg in self.args ] diff --git a/client/starwhale/api/_impl/service/types/types.py b/client/starwhale/api/_impl/service/types/types.py index f1a715e5b0..55e8d1a372 100644 --- a/client/starwhale/api/_impl/service/types/types.py +++ b/client/starwhale/api/_impl/service/types/types.py @@ -3,26 +3,18 @@ from typing import Any, Dict, List, Callable from starwhale.utils import console -from starwhale.base.models.base import SwBaseModel +from starwhale.base.client.models.models import ComponentSpec, ComponentSpecValueType Inputs = Any Outputs = Any -class ComponentSpec(SwBaseModel): - name: str - type: str - - def __hash__(self) -> int: - return hash((self.name, self.type)) - - class ServiceType(abc.ABC): """Protocol for service types.""" @property @abc.abstractmethod - def arg_types(self) -> Dict[str, Any]: + def arg_types(self) -> Dict[str, ComponentSpecValueType]: ... @property diff --git a/client/starwhale/base/client/models/models.py b/client/starwhale/base/client/models/models.py index 0ca5709e22..108085fa4d 100644 --- a/client/starwhale/base/client/models/models.py +++ b/client/starwhale/base/client/models/models.py @@ -729,6 +729,19 @@ class ResponseMessagePageInfoReportVo(SwBaseModel): data: PageInfoReportVo +class ComponentSpecValueType(Enum): + float = 'FLOAT' + int = 'INT' + string = 'STRING' + bool = 'BOOL' + list = 'LIST' + + +class ComponentSpec(SwBaseModel): + name: str + component_spec_value_type: ComponentSpecValueType + + class ContainerSpec(SwBaseModel): image: Optional[str] = None cmds: Optional[List[str]] = None @@ -757,25 +770,6 @@ class RuntimeResource(SwBaseModel): limit: Optional[float] = None -class StepSpec(SwBaseModel): - name: str - concurrency: Optional[int] = None - replicas: int - backoff_limit: Optional[int] = Field(None, alias='backoffLimit') - needs: Optional[List[str]] = None - resources: Optional[List[RuntimeResource]] = None - env: Optional[List[Env]] = None - expose: Optional[int] = None - virtual: Optional[bool] = None - job_name: Optional[str] = None - show_name: str - require_dataset: Optional[bool] = None - fine_tune: Optional[FineTune] = None - container_spec: Optional[ContainerSpec] = None - ext_cmd_args: Optional[str] = None - parameters_sig: Optional[List[ParameterSignature]] = None - - class DatasetVersionViewVo(SwBaseModel): id: str version_name: str = Field(..., alias='versionName') @@ -800,101 +794,6 @@ class ResponseMessageListDatasetViewVo(SwBaseModel): data: List[DatasetViewVo] -class ModelVersionVo(SwBaseModel): - latest: bool - tags: Optional[List[str]] = None - step_specs: List[StepSpec] = Field(..., alias='stepSpecs') - id: str - name: str - alias: str - size: Optional[int] = None - created_time: int = Field(..., alias='createdTime') - owner: Optional[UserVo] = None - shared: bool - built_in_runtime: Optional[str] = Field(None, alias='builtInRuntime') - draft: bool - - -class ModelVo(SwBaseModel): - id: str - name: str - created_time: int = Field(..., alias='createdTime') - owner: UserVo - version: ModelVersionVo - - -class PageInfoModelVo(SwBaseModel): - total: Optional[int] = None - list: Optional[List[ModelVo]] = None - page_num: Optional[int] = Field(None, alias='pageNum') - page_size: Optional[int] = Field(None, alias='pageSize') - size: Optional[int] = None - start_row: Optional[int] = Field(None, alias='startRow') - end_row: Optional[int] = Field(None, alias='endRow') - pages: Optional[int] = None - pre_page: Optional[int] = Field(None, alias='prePage') - next_page: Optional[int] = Field(None, alias='nextPage') - is_first_page: Optional[bool] = Field(None, alias='isFirstPage') - is_last_page: Optional[bool] = Field(None, alias='isLastPage') - has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') - has_next_page: Optional[bool] = Field(None, alias='hasNextPage') - navigate_pages: Optional[int] = Field(None, alias='navigatePages') - navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') - navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') - navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') - - -class ResponseMessagePageInfoModelVo(SwBaseModel): - code: str - message: str - data: PageInfoModelVo - - -class ModelInfoVo(SwBaseModel): - version_info: ModelVersionVo = Field(..., alias='versionInfo') - id: str - name: str - version_alias: str = Field(..., alias='versionAlias') - version_id: str = Field(..., alias='versionId') - version_name: str = Field(..., alias='versionName') - version_tag: Optional[str] = Field(None, alias='versionTag') - created_time: int = Field(..., alias='createdTime') - shared: int - - -class ResponseMessageModelInfoVo(SwBaseModel): - code: str - message: str - data: ModelInfoVo - - -class PageInfoModelVersionVo(SwBaseModel): - total: Optional[int] = None - list: Optional[List[ModelVersionVo]] = None - page_num: Optional[int] = Field(None, alias='pageNum') - page_size: Optional[int] = Field(None, alias='pageSize') - size: Optional[int] = None - start_row: Optional[int] = Field(None, alias='startRow') - end_row: Optional[int] = Field(None, alias='endRow') - pages: Optional[int] = None - pre_page: Optional[int] = Field(None, alias='prePage') - next_page: Optional[int] = Field(None, alias='nextPage') - is_first_page: Optional[bool] = Field(None, alias='isFirstPage') - is_last_page: Optional[bool] = Field(None, alias='isLastPage') - has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') - has_next_page: Optional[bool] = Field(None, alias='hasNextPage') - navigate_pages: Optional[int] = Field(None, alias='navigatePages') - navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') - navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') - navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') - - -class ResponseMessagePageInfoModelVersionVo(SwBaseModel): - code: str - message: str - data: PageInfoModelVersionVo - - class ResponseMessageMapStringListFileNode(SwBaseModel): code: str message: str @@ -953,65 +852,6 @@ class JobStatus(Enum): unknown = 'UNKNOWN' -class JobVo(SwBaseModel): - exposed_links: List[ExposedLinkVo] = Field(..., alias='exposedLinks') - id: str - uuid: str - model_name: str = Field(..., alias='modelName') - model_version: str = Field(..., alias='modelVersion') - model: ModelVo - job_name: Optional[str] = Field(None, alias='jobName') - job_type: Optional[JobType] = Field(None, alias='jobType') - datasets: Optional[List[str]] = None - dataset_list: Optional[List[DatasetVo]] = Field(None, alias='datasetList') - runtime: RuntimeVo - is_builtin_runtime: Optional[bool] = Field(None, alias='isBuiltinRuntime') - device: Optional[str] = None - device_amount: Optional[int] = Field(None, alias='deviceAmount') - owner: UserVo - created_time: int = Field(..., alias='createdTime') - stop_time: Optional[int] = Field(None, alias='stopTime') - job_status: JobStatus = Field(..., alias='jobStatus') - comment: Optional[str] = None - step_spec: Optional[str] = Field(None, alias='stepSpec') - resource_pool: str = Field(..., alias='resourcePool') - duration: Optional[int] = None - pinned_time: Optional[int] = Field(None, alias='pinnedTime') - - -class PageInfoJobVo(SwBaseModel): - total: Optional[int] = None - list: Optional[List[JobVo]] = None - page_num: Optional[int] = Field(None, alias='pageNum') - page_size: Optional[int] = Field(None, alias='pageSize') - size: Optional[int] = None - start_row: Optional[int] = Field(None, alias='startRow') - end_row: Optional[int] = Field(None, alias='endRow') - pages: Optional[int] = None - pre_page: Optional[int] = Field(None, alias='prePage') - next_page: Optional[int] = Field(None, alias='nextPage') - is_first_page: Optional[bool] = Field(None, alias='isFirstPage') - is_last_page: Optional[bool] = Field(None, alias='isLastPage') - has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') - has_next_page: Optional[bool] = Field(None, alias='hasNextPage') - navigate_pages: Optional[int] = Field(None, alias='navigatePages') - navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') - navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') - navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') - - -class ResponseMessagePageInfoJobVo(SwBaseModel): - code: str - message: str - data: PageInfoJobVo - - -class ResponseMessageJobVo(SwBaseModel): - code: str - message: str - data: JobVo - - class Status1(Enum): pending = 'PENDING' running = 'RUNNING' @@ -1300,49 +1140,6 @@ class ResponseMessageFineTuneSpaceVo(SwBaseModel): data: FineTuneSpaceVo -class FineTuneVo(SwBaseModel): - id: int - job: JobVo - train_datasets: List[DatasetVo] = Field(..., alias='trainDatasets') - validation_datasets: Optional[List[DatasetVo]] = Field( - None, alias='validationDatasets' - ) - target_model: ModelVo = Field(..., alias='targetModel') - - -class PageInfoFineTuneVo(SwBaseModel): - total: Optional[int] = None - list: Optional[List[FineTuneVo]] = None - page_num: Optional[int] = Field(None, alias='pageNum') - page_size: Optional[int] = Field(None, alias='pageSize') - size: Optional[int] = None - start_row: Optional[int] = Field(None, alias='startRow') - end_row: Optional[int] = Field(None, alias='endRow') - pages: Optional[int] = None - pre_page: Optional[int] = Field(None, alias='prePage') - next_page: Optional[int] = Field(None, alias='nextPage') - is_first_page: Optional[bool] = Field(None, alias='isFirstPage') - is_last_page: Optional[bool] = Field(None, alias='isLastPage') - has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') - has_next_page: Optional[bool] = Field(None, alias='hasNextPage') - navigate_pages: Optional[int] = Field(None, alias='navigatePages') - navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') - navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') - navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') - - -class ResponseMessagePageInfoFineTuneVo(SwBaseModel): - code: str - message: str - data: PageInfoFineTuneVo - - -class ResponseMessageFineTuneVo(SwBaseModel): - code: str - message: str - data: FineTuneVo - - class PanelPluginVo(SwBaseModel): id: str name: str @@ -1589,32 +1386,191 @@ class ResponseMessageListRuntimeViewVo(SwBaseModel): data: List[RuntimeViewVo] -class ModelVersionViewVo(SwBaseModel): - id: str - version_name: str = Field(..., alias='versionName') - alias: str +class ApiSpec(SwBaseModel): + uri: str + inference_type: Optional[str] = None + components: Optional[List[ComponentSpec]] = None + + +class ServiceSpec(SwBaseModel): + version: Optional[str] = None + title: Optional[str] = None + description: Optional[str] = None + apis: Optional[List[ApiSpec]] = None + + +class StepSpec(SwBaseModel): + name: str + concurrency: Optional[int] = None + replicas: int + backoff_limit: Optional[int] = Field(None, alias='backoffLimit') + needs: Optional[List[str]] = None + resources: Optional[List[RuntimeResource]] = None + env: Optional[List[Env]] = None + expose: Optional[int] = None + virtual: Optional[bool] = None + job_name: Optional[str] = None + show_name: str + require_dataset: Optional[bool] = None + fine_tune: Optional[FineTune] = None + container_spec: Optional[ContainerSpec] = None + ext_cmd_args: Optional[str] = None + parameters_sig: Optional[List[ParameterSignature]] = None + service_spec: Optional[ServiceSpec] = None + + +class ModelVersionVo(SwBaseModel): latest: bool tags: Optional[List[str]] = None - shared: int - draft: Optional[bool] = None step_specs: List[StepSpec] = Field(..., alias='stepSpecs') + id: str + name: str + alias: str + size: Optional[int] = None + created_time: int = Field(..., alias='createdTime') + owner: Optional[UserVo] = None + shared: bool built_in_runtime: Optional[str] = Field(None, alias='builtInRuntime') + draft: bool + + +class ModelVo(SwBaseModel): + id: str + name: str created_time: int = Field(..., alias='createdTime') + owner: UserVo + version: ModelVersionVo -class ModelViewVo(SwBaseModel): - owner_name: str = Field(..., alias='ownerName') - project_name: str = Field(..., alias='projectName') - model_id: str = Field(..., alias='modelId') +class PageInfoModelVo(SwBaseModel): + total: Optional[int] = None + list: Optional[List[ModelVo]] = None + page_num: Optional[int] = Field(None, alias='pageNum') + page_size: Optional[int] = Field(None, alias='pageSize') + size: Optional[int] = None + start_row: Optional[int] = Field(None, alias='startRow') + end_row: Optional[int] = Field(None, alias='endRow') + pages: Optional[int] = None + pre_page: Optional[int] = Field(None, alias='prePage') + next_page: Optional[int] = Field(None, alias='nextPage') + is_first_page: Optional[bool] = Field(None, alias='isFirstPage') + is_last_page: Optional[bool] = Field(None, alias='isLastPage') + has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') + has_next_page: Optional[bool] = Field(None, alias='hasNextPage') + navigate_pages: Optional[int] = Field(None, alias='navigatePages') + navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') + navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') + navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') + + +class ResponseMessagePageInfoModelVo(SwBaseModel): + code: str + message: str + data: PageInfoModelVo + + +class ModelInfoVo(SwBaseModel): + version_info: ModelVersionVo = Field(..., alias='versionInfo') + id: str + name: str + version_alias: str = Field(..., alias='versionAlias') + version_id: str = Field(..., alias='versionId') + version_name: str = Field(..., alias='versionName') + version_tag: Optional[str] = Field(None, alias='versionTag') + created_time: int = Field(..., alias='createdTime') + shared: int + + +class ResponseMessageModelInfoVo(SwBaseModel): + code: str + message: str + data: ModelInfoVo + + +class PageInfoModelVersionVo(SwBaseModel): + total: Optional[int] = None + list: Optional[List[ModelVersionVo]] = None + page_num: Optional[int] = Field(None, alias='pageNum') + page_size: Optional[int] = Field(None, alias='pageSize') + size: Optional[int] = None + start_row: Optional[int] = Field(None, alias='startRow') + end_row: Optional[int] = Field(None, alias='endRow') + pages: Optional[int] = None + pre_page: Optional[int] = Field(None, alias='prePage') + next_page: Optional[int] = Field(None, alias='nextPage') + is_first_page: Optional[bool] = Field(None, alias='isFirstPage') + is_last_page: Optional[bool] = Field(None, alias='isLastPage') + has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') + has_next_page: Optional[bool] = Field(None, alias='hasNextPage') + navigate_pages: Optional[int] = Field(None, alias='navigatePages') + navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') + navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') + navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') + + +class ResponseMessagePageInfoModelVersionVo(SwBaseModel): + code: str + message: str + data: PageInfoModelVersionVo + + +class JobVo(SwBaseModel): + exposed_links: List[ExposedLinkVo] = Field(..., alias='exposedLinks') + id: str + uuid: str model_name: str = Field(..., alias='modelName') - shared: bool - versions: List[ModelVersionViewVo] + model_version: str = Field(..., alias='modelVersion') + model: ModelVo + job_name: Optional[str] = Field(None, alias='jobName') + job_type: Optional[JobType] = Field(None, alias='jobType') + datasets: Optional[List[str]] = None + dataset_list: Optional[List[DatasetVo]] = Field(None, alias='datasetList') + runtime: RuntimeVo + is_builtin_runtime: Optional[bool] = Field(None, alias='isBuiltinRuntime') + device: Optional[str] = None + device_amount: Optional[int] = Field(None, alias='deviceAmount') + owner: UserVo + created_time: int = Field(..., alias='createdTime') + stop_time: Optional[int] = Field(None, alias='stopTime') + job_status: JobStatus = Field(..., alias='jobStatus') + comment: Optional[str] = None + step_spec: Optional[str] = Field(None, alias='stepSpec') + resource_pool: str = Field(..., alias='resourcePool') + duration: Optional[int] = None + pinned_time: Optional[int] = Field(None, alias='pinnedTime') -class ResponseMessageListModelViewVo(SwBaseModel): +class PageInfoJobVo(SwBaseModel): + total: Optional[int] = None + list: Optional[List[JobVo]] = None + page_num: Optional[int] = Field(None, alias='pageNum') + page_size: Optional[int] = Field(None, alias='pageSize') + size: Optional[int] = None + start_row: Optional[int] = Field(None, alias='startRow') + end_row: Optional[int] = Field(None, alias='endRow') + pages: Optional[int] = None + pre_page: Optional[int] = Field(None, alias='prePage') + next_page: Optional[int] = Field(None, alias='nextPage') + is_first_page: Optional[bool] = Field(None, alias='isFirstPage') + is_last_page: Optional[bool] = Field(None, alias='isLastPage') + has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') + has_next_page: Optional[bool] = Field(None, alias='hasNextPage') + navigate_pages: Optional[int] = Field(None, alias='navigatePages') + navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') + navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') + navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') + + +class ResponseMessagePageInfoJobVo(SwBaseModel): code: str message: str - data: List[ModelViewVo] + data: PageInfoJobVo + + +class ResponseMessageJobVo(SwBaseModel): + code: str + message: str + data: JobVo class PageInfoTaskVo(SwBaseModel): @@ -1658,6 +1614,49 @@ class ResponseMessageGraph(SwBaseModel): data: Graph +class FineTuneVo(SwBaseModel): + id: int + job: JobVo + train_datasets: List[DatasetVo] = Field(..., alias='trainDatasets') + validation_datasets: Optional[List[DatasetVo]] = Field( + None, alias='validationDatasets' + ) + target_model: ModelVo = Field(..., alias='targetModel') + + +class PageInfoFineTuneVo(SwBaseModel): + total: Optional[int] = None + list: Optional[List[FineTuneVo]] = None + page_num: Optional[int] = Field(None, alias='pageNum') + page_size: Optional[int] = Field(None, alias='pageSize') + size: Optional[int] = None + start_row: Optional[int] = Field(None, alias='startRow') + end_row: Optional[int] = Field(None, alias='endRow') + pages: Optional[int] = None + pre_page: Optional[int] = Field(None, alias='prePage') + next_page: Optional[int] = Field(None, alias='nextPage') + is_first_page: Optional[bool] = Field(None, alias='isFirstPage') + is_last_page: Optional[bool] = Field(None, alias='isLastPage') + has_previous_page: Optional[bool] = Field(None, alias='hasPreviousPage') + has_next_page: Optional[bool] = Field(None, alias='hasNextPage') + navigate_pages: Optional[int] = Field(None, alias='navigatePages') + navigatepage_nums: Optional[List[int]] = Field(None, alias='navigatepageNums') + navigate_first_page: Optional[int] = Field(None, alias='navigateFirstPage') + navigate_last_page: Optional[int] = Field(None, alias='navigateLastPage') + + +class ResponseMessagePageInfoFineTuneVo(SwBaseModel): + code: str + message: str + data: PageInfoFineTuneVo + + +class ResponseMessageFineTuneVo(SwBaseModel): + code: str + message: str + data: FineTuneVo + + class PageInfoPanelPluginVo(SwBaseModel): total: Optional[int] = None list: Optional[List[PanelPluginVo]] = None @@ -1704,6 +1703,34 @@ class ResponseMessageListProjectMemberVo(SwBaseModel): data: List[ProjectMemberVo] +class ModelVersionViewVo(SwBaseModel): + id: str + version_name: str = Field(..., alias='versionName') + alias: str + latest: bool + tags: Optional[List[str]] = None + shared: int + draft: Optional[bool] = None + step_specs: List[StepSpec] = Field(..., alias='stepSpecs') + built_in_runtime: Optional[str] = Field(None, alias='builtInRuntime') + created_time: int = Field(..., alias='createdTime') + + +class ModelViewVo(SwBaseModel): + owner_name: str = Field(..., alias='ownerName') + project_name: str = Field(..., alias='projectName') + model_id: str = Field(..., alias='modelId') + model_name: str = Field(..., alias='modelName') + shared: bool + versions: List[ModelVersionViewVo] + + +class ResponseMessageListModelViewVo(SwBaseModel): + code: str + message: str + data: List[ModelViewVo] + + class ColumnSchemaDesc(SwBaseModel): name: Optional[str] = None index: Optional[int] = None diff --git a/client/starwhale/consts/__init__.py b/client/starwhale/consts/__init__.py index 32e800b855..c4526dfd21 100644 --- a/client/starwhale/consts/__init__.py +++ b/client/starwhale/consts/__init__.py @@ -21,7 +21,6 @@ # evaluation related constants DEFAULT_JOBS_FILE_NAME = "jobs.yaml" -EVALUATION_SVC_META_FILE_NAME = "svc.json" # auto generated evaluation panel layout file name from yaml or local console EVALUATION_PANEL_LAYOUT_JSON_FILE_NAME = "eval_panel_layout.json" # user defined evaluation panel layout file name diff --git a/client/starwhale/core/model/model.py b/client/starwhale/core/model/model.py index 60fd7706f6..a8870443cc 100644 --- a/client/starwhale/core/model/model.py +++ b/client/starwhale/core/model/model.py @@ -40,7 +40,6 @@ DEFAULT_RESOURCE_POOL, DEFAULT_JOBS_FILE_NAME, DEFAULT_STARWHALE_API_VERSION, - EVALUATION_SVC_META_FILE_NAME, EVALUATION_PANEL_LAYOUT_JSON_FILE_NAME, EVALUATION_PANEL_LAYOUT_YAML_FILE_NAME, DEFAULT_FILE_SIZE_THRESHOLD_TO_TAR_IN_MODEL, @@ -263,11 +262,9 @@ def _gen_model_serving(self, search_modules: t.List[str], workdir: Path) -> None console.debug(f"generating model serving config for {self.uri} ...") # render spec svc = self._get_service(search_modules, workdir) - file = self.store.hidden_sw_dir / EVALUATION_SVC_META_FILE_NAME spec = svc.get_spec() if spec is None: return - ensure_file(file, json.dumps(spec.to_dict(), indent=4), parents=True) # make virtual handler to make the model serving can be used in model run func = self._serve_handler @@ -283,6 +280,8 @@ def _gen_model_serving(self, search_modules: t.List[str], workdir: Path) -> None extra_kwargs={ "search_modules": search_modules, }, + # embed the model serving spec into model run spec + service_spec=spec, ) Handler._register(h, func) diff --git a/client/tests/core/test_model.py b/client/tests/core/test_model.py index 7e9c16f694..4fe675a074 100644 --- a/client/tests/core/test_model.py +++ b/client/tests/core/test_model.py @@ -216,6 +216,15 @@ def test_build_workflow( expose=8080, virtual=True, extra_kwargs={"search_modules": ["mnist.evaluator:MNISTInference"]}, + service_spec=ServiceSpec( + version="0.0.1", + apis=[ + ApiSpec( + uri="", + inference_type="question_answering", + ) + ], + ), ), ], } diff --git a/client/tests/sdk/test_service.py b/client/tests/sdk/test_service.py index 2059d30555..a843e39ecf 100644 --- a/client/tests/sdk/test_service.py +++ b/client/tests/sdk/test_service.py @@ -7,6 +7,7 @@ from tests import ROOT_DIR, BaseTestCase from starwhale.core.model.model import StandaloneModel from starwhale.api._impl.service.service import Service +from starwhale.base.client.models.models import ComponentSpecValueType from starwhale.api._impl.service.types.types import ComponentSpec @@ -39,12 +40,22 @@ def test_default_class(self): assert len(spec.apis) == 1 assert spec.apis[0].uri == "cmp" assert spec.apis[0].inference_type == "llm_chat" - components = spec.apis[0].components_hint - assert set(components) == { - ComponentSpec(name="user_input", type="str"), - ComponentSpec(name="history", type="list"), - ComponentSpec(name="temperature", type="float"), - } + components = spec.apis[0].components + assert len(components) == 3 + for c in [ + ComponentSpec( + name="user_input", + component_spec_value_type=ComponentSpecValueType.string, + ), + ComponentSpec( + name="history", component_spec_value_type=ComponentSpecValueType.list + ), + ComponentSpec( + name="temperature", + component_spec_value_type=ComponentSpecValueType.float, + ), + ]: + assert c in components def test_class_without_api(self): svc = StandaloneModel._get_service(["no_api:NoApi"], self.root) diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/StepSpec.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/StepSpec.java index bfe1a4c980..cb2240f015 100644 --- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/StepSpec.java +++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/StepSpec.java @@ -16,6 +16,7 @@ package ai.starwhale.mlops.domain.job.spec; +import ai.starwhale.mlops.domain.job.spec.svc.ServiceSpec; import ai.starwhale.mlops.domain.runtime.RuntimeResource; import ai.starwhale.mlops.exception.SwValidationException; import ai.starwhale.mlops.exception.SwValidationException.ValidSubject; @@ -103,6 +104,9 @@ public static class FineTune { @JsonProperty("parameters_sig") private List parametersSig; + @JsonProperty("service_spec") + private ServiceSpec serviceSpec; + public void verifyStepSpecArgs() { if (CollectionUtils.isEmpty(this.getParametersSig())) { return; diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ApiSpec.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ApiSpec.java new file mode 100644 index 0000000000..af814a6dc2 --- /dev/null +++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ApiSpec.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Starwhale, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.starwhale.mlops.domain.job.spec.svc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ApiSpec { + @NotNull + @JsonProperty("uri") + private String uri; + + @JsonProperty("inference_type") + private String inferenceType; + + @JsonProperty("components") + private List components; +} diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ComponentSpec.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ComponentSpec.java new file mode 100644 index 0000000000..71a7460315 --- /dev/null +++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ComponentSpec.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 Starwhale, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.starwhale.mlops.domain.job.spec.svc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ComponentSpec { + @NotNull + @JsonProperty("name") + String name; + + public enum ComponentSpecValueType { + FLOAT, + INT, + STRING, + BOOL, + LIST, + } + + @NotNull + @JsonProperty("component_spec_value_type") + ComponentSpecValueType componentSpecValueType; +} diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ServiceSpec.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ServiceSpec.java new file mode 100644 index 0000000000..0da1a7a834 --- /dev/null +++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/spec/svc/ServiceSpec.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Starwhale, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.starwhale.mlops.domain.job.spec.svc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ServiceSpec { + @JsonProperty("version") + private String version; + + @JsonProperty("title") + private String title; + + @JsonProperty("description") + private String description; + + @JsonProperty("apis") + private List apis; +}