hw-cloudrobo-infer 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hw_cloudrobo_infer-0.2.0/PKG-INFO +21 -0
- hw_cloudrobo_infer-0.2.0/README.md +16 -0
- hw_cloudrobo_infer-0.2.0/pyproject.toml +35 -0
- hw_cloudrobo_infer-0.2.0/setup.cfg +4 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/__init__.py +3 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/cli.py +329 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/client.py +59 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/validators/__init__.py +3 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/validators/cli_callbacks.py +349 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/validators/rules.py +1085 -0
- hw_cloudrobo_infer-0.2.0/src/cloudrobo_infer/validators/validator.py +234 -0
- hw_cloudrobo_infer-0.2.0/src/hw_cloudrobo_infer.egg-info/PKG-INFO +21 -0
- hw_cloudrobo_infer-0.2.0/src/hw_cloudrobo_infer.egg-info/SOURCES.txt +18 -0
- hw_cloudrobo_infer-0.2.0/src/hw_cloudrobo_infer.egg-info/dependency_links.txt +1 -0
- hw_cloudrobo_infer-0.2.0/src/hw_cloudrobo_infer.egg-info/entry_points.txt +2 -0
- hw_cloudrobo_infer-0.2.0/src/hw_cloudrobo_infer.egg-info/requires.txt +3 -0
- hw_cloudrobo_infer-0.2.0/src/hw_cloudrobo_infer.egg-info/top_level.txt +1 -0
- hw_cloudrobo_infer-0.2.0/tests/test_infer_cli.py +456 -0
- hw_cloudrobo_infer-0.2.0/tests/test_infer_client.py +138 -0
- hw_cloudrobo_infer-0.2.0/tests/test_infer_validator.py +332 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hw-cloudrobo-infer
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CloudRobo Inference Service SDK and CLI
|
|
5
|
+
Author-email: Huawei Cloud CloudRobo Team <hwcloudrobo@huawei.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Requires-Dist: click>=8.0
|
|
20
|
+
Requires-Dist: hw-cloudrobo-core>=0.1.0
|
|
21
|
+
Requires-Dist: hw-cloudrobo-workspace>=0.1.0
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# cloudrobo-infer
|
|
2
|
+
|
|
3
|
+
推理服务模块,提供推理服务的创建、查询、启停和模型部署等待。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e packages/cloudrobo-infer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 文档
|
|
12
|
+
|
|
13
|
+
- [模块概览](docs/index.md)
|
|
14
|
+
- [CLI 命令](docs/commands.md)
|
|
15
|
+
- [使用示例](docs/examples.md)
|
|
16
|
+
- [开发指南](docs/development.md)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools==83.0.0", "packaging==26.2", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hw-cloudrobo-infer"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "CloudRobo Inference Service SDK and CLI"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
authors = [{name = "Huawei Cloud CloudRobo Team", email = "hwcloudrobo@huawei.com"}]
|
|
11
|
+
license = {text = "Apache-2.0"}
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: Apache Software License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.8",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"click>=8.0",
|
|
27
|
+
"hw-cloudrobo-core>=0.1.0",
|
|
28
|
+
"hw-cloudrobo-workspace>=0.1.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.entry-points."cloudrobo.groups"]
|
|
32
|
+
infer = "cloudrobo_infer.cli:infer"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from cloudrobo_core.cli.cli_utils import get_client, out
|
|
5
|
+
|
|
6
|
+
from .client import InferClient
|
|
7
|
+
|
|
8
|
+
# isort: off
|
|
9
|
+
# ===== VALIDATOR IMPORTS =====
|
|
10
|
+
from .validators.cli_callbacks import (
|
|
11
|
+
_validate_cmd,
|
|
12
|
+
_validate_deploy_timeout_minutes,
|
|
13
|
+
_validate_description,
|
|
14
|
+
_validate_end_time,
|
|
15
|
+
_validate_envs,
|
|
16
|
+
_validate_files,
|
|
17
|
+
_validate_flavor,
|
|
18
|
+
_validate_image_swr_url,
|
|
19
|
+
_validate_keywords,
|
|
20
|
+
_validate_limit,
|
|
21
|
+
_validate_line_num,
|
|
22
|
+
_validate_liveness_health,
|
|
23
|
+
_validate_model,
|
|
24
|
+
_validate_model_ext_metadata,
|
|
25
|
+
_validate_model_id,
|
|
26
|
+
_validate_model_name,
|
|
27
|
+
_validate_model_version_id,
|
|
28
|
+
_validate_model_version_name,
|
|
29
|
+
_validate_name,
|
|
30
|
+
_validate_offset,
|
|
31
|
+
_validate_pool_id,
|
|
32
|
+
_validate_pool_type,
|
|
33
|
+
_validate_readiness_health,
|
|
34
|
+
_validate_service_id,
|
|
35
|
+
_validate_service_invoke,
|
|
36
|
+
_validate_skill_config,
|
|
37
|
+
_validate_sort_dir,
|
|
38
|
+
_validate_sort_key,
|
|
39
|
+
_validate_start_time,
|
|
40
|
+
_validate_startup_health,
|
|
41
|
+
_validate_status,
|
|
42
|
+
_validate_stop_schedule,
|
|
43
|
+
_validate_user_id,
|
|
44
|
+
_validate_user_name,
|
|
45
|
+
_validate_workspace_id,
|
|
46
|
+
)
|
|
47
|
+
# ===== END VALIDATOR IMPORTS =====
|
|
48
|
+
# isort: on
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.group()
|
|
55
|
+
def infer():
|
|
56
|
+
"""推理服务命令组"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _parse_json_options(ctx, target, options):
|
|
63
|
+
for key, raw in options.items():
|
|
64
|
+
if raw:
|
|
65
|
+
try:
|
|
66
|
+
target[key] = json.loads(raw)
|
|
67
|
+
except json.JSONDecodeError as e:
|
|
68
|
+
raise click.BadParameter(f"Invalid JSON for '{key}': {e}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _parse_required_json(ctx, raw, key):
|
|
72
|
+
if not raw:
|
|
73
|
+
raise click.BadParameter(f"'{key}' is required and must be a JSON object")
|
|
74
|
+
try:
|
|
75
|
+
return json.loads(raw)
|
|
76
|
+
except json.JSONDecodeError as e:
|
|
77
|
+
raise click.BadParameter(f"Invalid JSON for '{key}': {e}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@infer.command("create")
|
|
81
|
+
@click.option("--cmd", default=None, callback=_validate_cmd, help='启动命令')
|
|
82
|
+
@click.option("--deploy-timeout-minutes", type=click.IntRange(1, 300), callback=_validate_deploy_timeout_minutes, help='部署超时时间')
|
|
83
|
+
@click.option("--description", default=None, callback=_validate_description, help='描述')
|
|
84
|
+
@click.option("--envs-json", default=None, callback=_validate_envs, help='环境变量')
|
|
85
|
+
@click.option("--files-json", default=None, callback=_validate_files, help='文件挂载')
|
|
86
|
+
@click.option("--flavor", required=True, callback=_validate_flavor, help='资源规格')
|
|
87
|
+
@click.option("--image-swr-url", default=None, callback=_validate_image_swr_url, help='镜像地址')
|
|
88
|
+
@click.option("--internet-access-enable", is_flag=True, default=False, help='是否开启公网访问')
|
|
89
|
+
@click.option("--liveness-health-json", default=None, callback=_validate_liveness_health, help='模型服务健康检查')
|
|
90
|
+
@click.option("--model-json", required=True, callback=_validate_model, help='模型信息')
|
|
91
|
+
@click.option("--model-ext-metadata", default=None, callback=_validate_model_ext_metadata, help='模型扩展元数据,JSON/YAML 格式的 r2c 配置信息。')
|
|
92
|
+
@click.option("--name", required=True, callback=_validate_name, help='服务名称')
|
|
93
|
+
@click.option("--pool-id", required=True, callback=_validate_pool_id, help='资源池ID')
|
|
94
|
+
@click.option("--pool-type", type=click.Choice(['DEDICATED', 'SHARED'], case_sensitive=False), callback=_validate_pool_type, required=True, help='资源池类型')
|
|
95
|
+
@click.option("--readiness-health-json", default=None, callback=_validate_readiness_health, help='模型服务健康检查')
|
|
96
|
+
@click.option("--service-invoke-json", default=None, callback=_validate_service_invoke, help='服务调用配置')
|
|
97
|
+
@click.option("--skill-config-json", default=None, callback=_validate_skill_config, help='技能配置')
|
|
98
|
+
@click.option("--startup-health-json", default=None, callback=_validate_startup_health, help='模型服务健康检查')
|
|
99
|
+
@click.option("--stop-schedule-json", default=None, callback=_validate_stop_schedule, help='定时停止配置')
|
|
100
|
+
@click.option("--workspace-id", required=False, default=None, callback=_validate_workspace_id, help='工作空间ID')
|
|
101
|
+
@click.option("--dry-run", is_flag=True)
|
|
102
|
+
@click.pass_context
|
|
103
|
+
def create_infer_service(ctx, cmd, deploy_timeout_minutes, description, envs_json,
|
|
104
|
+
files_json, flavor, image_swr_url, internet_access_enable,
|
|
105
|
+
liveness_health_json, model_json, model_ext_metadata, name,
|
|
106
|
+
pool_id, pool_type, readiness_health_json,
|
|
107
|
+
service_invoke_json, skill_config_json, startup_health_json,
|
|
108
|
+
stop_schedule_json, workspace_id, dry_run):
|
|
109
|
+
"""创建推理服务(部署模型)"""
|
|
110
|
+
req = {
|
|
111
|
+
"name": name,
|
|
112
|
+
"flavor": flavor,
|
|
113
|
+
"model": _parse_required_json(ctx, model_json, "model"),
|
|
114
|
+
"workspace_id": workspace_id,
|
|
115
|
+
"pool_id": pool_id,
|
|
116
|
+
"pool_type": pool_type.upper(),
|
|
117
|
+
}
|
|
118
|
+
if description is not None:
|
|
119
|
+
req["description"] = description
|
|
120
|
+
if image_swr_url is not None:
|
|
121
|
+
req["image_swr_url"] = image_swr_url
|
|
122
|
+
if cmd is not None:
|
|
123
|
+
req["cmd"] = cmd
|
|
124
|
+
if deploy_timeout_minutes is not None:
|
|
125
|
+
req["deploy_timeout_minutes"] = deploy_timeout_minutes
|
|
126
|
+
if model_ext_metadata is not None:
|
|
127
|
+
req["model_ext_metadata"] = model_ext_metadata
|
|
128
|
+
if internet_access_enable is not None:
|
|
129
|
+
req["internet_access_enable"] = str(internet_access_enable).lower() in ("1", "true", "yes")
|
|
130
|
+
_parse_json_options(ctx, req, {
|
|
131
|
+
"envs": envs_json,
|
|
132
|
+
"stop_schedule": stop_schedule_json,
|
|
133
|
+
"service_invoke": service_invoke_json,
|
|
134
|
+
"skill_config": skill_config_json,
|
|
135
|
+
"files": files_json,
|
|
136
|
+
"startup_health": startup_health_json,
|
|
137
|
+
"readiness_health": readiness_health_json,
|
|
138
|
+
"liveness_health": liveness_health_json,
|
|
139
|
+
})
|
|
140
|
+
if dry_run:
|
|
141
|
+
click.echo(f"[DRY-RUN] create_infer_service({json.dumps(req, ensure_ascii=False)})")
|
|
142
|
+
return
|
|
143
|
+
client = get_client(ctx, InferClient)
|
|
144
|
+
result = client.create_infer_service(req)
|
|
145
|
+
out(result)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@infer.command("list")
|
|
149
|
+
@click.option("--limit", type=click.IntRange(1, 50), default=10, callback=_validate_limit, help='每页数据条数')
|
|
150
|
+
@click.option("--offset", type=click.IntRange(0, 1000), default=0, callback=_validate_offset, help='分页页码偏移量')
|
|
151
|
+
@click.option("--sort-key", type=click.Choice(['create_at', 'created_at', 'update_at', 'updated_at'], case_sensitive=False), callback=_validate_sort_key, help='排序字段,支持 create_at / update_at / created_at / updated_at')
|
|
152
|
+
@click.option("--sort-dir", type=click.Choice(['ASC', 'DESC'], case_sensitive=False), callback=_validate_sort_dir, help='排序方向,ASC正序 / DESC倒序')
|
|
153
|
+
@click.option("--name", default=None, callback=_validate_name, help='推理服务名称模糊查询')
|
|
154
|
+
@click.option("--workspace-id", required=False, default=None, callback=_validate_workspace_id, help='工作空间ID')
|
|
155
|
+
@click.option("--status", default=None, callback=_validate_status, help='根据服务状态查询相关推理服务,支持多选')
|
|
156
|
+
@click.option("--model-id", default=None, callback=_validate_model_id, help='模型资产ID筛选')
|
|
157
|
+
@click.option("--model-name", default=None, callback=_validate_model_name, help='模型资产名称筛选')
|
|
158
|
+
@click.option("--model-version-id", default=None, callback=_validate_model_version_id, help='模型版本ID筛选')
|
|
159
|
+
@click.option("--model-version-name", default=None, callback=_validate_model_version_name, help='模型版本名称筛选')
|
|
160
|
+
@click.option("--user-name", default=None, callback=_validate_user_name, help='创建人名称筛选')
|
|
161
|
+
@click.option("--user-id", default=None, callback=_validate_user_id, help='创建人ID筛选')
|
|
162
|
+
@click.option("--contain-ext-metadata", is_flag=True, default=None, help='是否只返回包含 model_ext_metadata 的记录。省略=返回全部;true=只返回有 model_ext_metadata 的记录;false=只返回没有 model_ext_metadata 的记录')
|
|
163
|
+
@click.pass_context
|
|
164
|
+
def list_infer_services(ctx, limit, offset, sort_key, sort_dir, name, workspace_id,
|
|
165
|
+
status, model_id, model_name, model_version_id,
|
|
166
|
+
model_version_name, user_name, user_id, contain_ext_metadata):
|
|
167
|
+
"""查询推理服务列表"""
|
|
168
|
+
client = get_client(ctx, InferClient)
|
|
169
|
+
params = {}
|
|
170
|
+
if limit is not None:
|
|
171
|
+
params["limit"] = limit
|
|
172
|
+
if offset is not None:
|
|
173
|
+
params["offset"] = offset
|
|
174
|
+
if sort_key:
|
|
175
|
+
params["sort_key"] = sort_key
|
|
176
|
+
if sort_dir:
|
|
177
|
+
params["sort_dir"] = sort_dir
|
|
178
|
+
if name:
|
|
179
|
+
params["name"] = name
|
|
180
|
+
if workspace_id:
|
|
181
|
+
params["workspace_id"] = workspace_id
|
|
182
|
+
if status:
|
|
183
|
+
params["status"] = status
|
|
184
|
+
if model_id:
|
|
185
|
+
params["model_id"] = model_id
|
|
186
|
+
if model_name:
|
|
187
|
+
params["model_name"] = model_name
|
|
188
|
+
if model_version_id:
|
|
189
|
+
params["model_version_id"] = model_version_id
|
|
190
|
+
if model_version_name:
|
|
191
|
+
params["model_version_name"] = model_version_name
|
|
192
|
+
if user_name:
|
|
193
|
+
params["user_name"] = user_name
|
|
194
|
+
if user_id:
|
|
195
|
+
params["user_id"] = user_id
|
|
196
|
+
if contain_ext_metadata is not None:
|
|
197
|
+
params["contain_ext_metadata"] = contain_ext_metadata
|
|
198
|
+
result = client.list_infer_services(**params)
|
|
199
|
+
out(result)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@infer.command("show")
|
|
203
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
204
|
+
@click.pass_context
|
|
205
|
+
def show_infer_service(ctx, service_id):
|
|
206
|
+
"""查询推理服务详情"""
|
|
207
|
+
client = get_client(ctx, InferClient)
|
|
208
|
+
result = client.show_infer_service(service_id)
|
|
209
|
+
out(result)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@infer.command("update")
|
|
213
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
214
|
+
@click.option("--description", default=None, callback=_validate_description, help='描述')
|
|
215
|
+
@click.option("--model-ext-metadata", default=None, callback=_validate_model_ext_metadata, help='模型扩展元数据,JSON/YAML 格式的 r2c 配置信息。')
|
|
216
|
+
@click.option("--dry-run", is_flag=True)
|
|
217
|
+
@click.pass_context
|
|
218
|
+
def update_infer_service(ctx, service_id, description, model_ext_metadata, dry_run):
|
|
219
|
+
"""更新推理服务配置"""
|
|
220
|
+
req = {}
|
|
221
|
+
if description is not None:
|
|
222
|
+
req["description"] = description
|
|
223
|
+
if model_ext_metadata is not None:
|
|
224
|
+
req["model_ext_metadata"] = model_ext_metadata
|
|
225
|
+
if dry_run:
|
|
226
|
+
click.echo(
|
|
227
|
+
f"[DRY-RUN] update_infer_service(service_id={service_id}, "
|
|
228
|
+
f"{json.dumps(req, ensure_ascii=False)})"
|
|
229
|
+
)
|
|
230
|
+
return
|
|
231
|
+
client = get_client(ctx, InferClient)
|
|
232
|
+
result = client.update_infer_service(service_id, req)
|
|
233
|
+
out(result)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@infer.command("delete")
|
|
237
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
238
|
+
@click.option("--dry-run", is_flag=True)
|
|
239
|
+
@click.pass_context
|
|
240
|
+
def delete_infer_service(ctx, service_id, dry_run):
|
|
241
|
+
"""删除推理服务"""
|
|
242
|
+
if dry_run:
|
|
243
|
+
click.echo(f"[DRY-RUN] delete_infer_service(service_id={service_id})")
|
|
244
|
+
return
|
|
245
|
+
client = get_client(ctx, InferClient)
|
|
246
|
+
client.delete_infer_service(service_id)
|
|
247
|
+
out(f"deleted: service_id={service_id}")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@infer.command("start")
|
|
251
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
252
|
+
@click.option("--dry-run", is_flag=True)
|
|
253
|
+
@click.pass_context
|
|
254
|
+
def start_infer_service(ctx, service_id, dry_run):
|
|
255
|
+
"""启动推理服务"""
|
|
256
|
+
if dry_run:
|
|
257
|
+
click.echo(f"[DRY-RUN] start_infer_service(service_id={service_id})")
|
|
258
|
+
return
|
|
259
|
+
client = get_client(ctx, InferClient)
|
|
260
|
+
result = client.start_infer_service(service_id)
|
|
261
|
+
out(result)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@infer.command("stop")
|
|
265
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
266
|
+
@click.option("--dry-run", is_flag=True)
|
|
267
|
+
@click.pass_context
|
|
268
|
+
def stop_infer_service(ctx, service_id, dry_run):
|
|
269
|
+
"""停止推理服务"""
|
|
270
|
+
if dry_run:
|
|
271
|
+
click.echo(f"[DRY-RUN] stop_infer_service(service_id={service_id})")
|
|
272
|
+
return
|
|
273
|
+
client = get_client(ctx, InferClient)
|
|
274
|
+
result = client.stop_infer_service(service_id)
|
|
275
|
+
out(result)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@infer.command("list-logs")
|
|
279
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
280
|
+
@click.option("--end-time", type=click.IntRange(0, 32503680000000), required=True, callback=_validate_end_time, help='搜索日志的结束时间。')
|
|
281
|
+
@click.option("--highlight", is_flag=True, default=None, help='在查询结果中日志关键词是否高亮显示。')
|
|
282
|
+
@click.option("--is-count", is_flag=True, default=None, help='在查询结果中是否统计日志条数。')
|
|
283
|
+
@click.option("--is-desc", is_flag=True, default=None, help='表示日志查询的顺序,当前支持顺序(false)或倒序查询(true)。')
|
|
284
|
+
@click.option("--keywords", default=None, callback=_validate_keywords, help='支持关键词精确搜索。关键词指相邻两个分词之间的单词。')
|
|
285
|
+
@click.option("--limit", type=click.IntRange(1, 5000), callback=_validate_limit, help='每次查询的日志条数。最小值:1,最大值:5000。')
|
|
286
|
+
@click.option("--line-num", default=None, callback=_validate_line_num, help='日志单行序列号,标识日志上报顺序,通常用于分页查询和日志数据的有序处理。分页查询需要使用该参数,用于从上次查询结束的问题继续查询。该参数从上次查询的返回结果中获取。')
|
|
287
|
+
@click.option("--start-time", type=click.IntRange(0, 32503680000000), required=True, callback=_validate_start_time, help='搜索日志的起始时间。')
|
|
288
|
+
@click.option("--dry-run", is_flag=True)
|
|
289
|
+
@click.pass_context
|
|
290
|
+
def list_infer_service_logs(ctx, service_id, start_time, end_time, limit, is_desc,
|
|
291
|
+
line_num, is_count, keywords, highlight, dry_run):
|
|
292
|
+
"""查询推理服务日志"""
|
|
293
|
+
req = {"start_time": start_time, "end_time": end_time}
|
|
294
|
+
if limit is not None:
|
|
295
|
+
req["limit"] = limit
|
|
296
|
+
if is_desc is not None:
|
|
297
|
+
req["is_desc"] = True
|
|
298
|
+
if line_num:
|
|
299
|
+
req["line_num"] = line_num
|
|
300
|
+
if is_count is not None:
|
|
301
|
+
req["is_count"] = True
|
|
302
|
+
if keywords:
|
|
303
|
+
req["keywords"] = keywords
|
|
304
|
+
if highlight is not None:
|
|
305
|
+
req["highlight"] = True
|
|
306
|
+
if dry_run:
|
|
307
|
+
click.echo(
|
|
308
|
+
f"[DRY-RUN] list_infer_service_logs(service_id={service_id}, "
|
|
309
|
+
f"{json.dumps(req, ensure_ascii=False)})"
|
|
310
|
+
)
|
|
311
|
+
return
|
|
312
|
+
client = get_client(ctx, InferClient)
|
|
313
|
+
result = client.list_infer_service_logs(service_id, req)
|
|
314
|
+
out(result)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@infer.command("wait-deploy")
|
|
318
|
+
@click.option("--service-id", required=True, callback=_validate_service_id, help='推理服务唯一标识ID')
|
|
319
|
+
@click.option("--timeout", type=click.IntRange(1, 3600), default=600, help="部署等待超时时间(秒),默认600")
|
|
320
|
+
@click.pass_context
|
|
321
|
+
def wait_deploy(ctx, service_id, timeout):
|
|
322
|
+
"""等待推理服务部署完成"""
|
|
323
|
+
client = get_client(ctx, InferClient)
|
|
324
|
+
try:
|
|
325
|
+
result = client.wait_deploy(service_id, timeout=timeout)
|
|
326
|
+
except RuntimeError as e:
|
|
327
|
+
click.echo(json.dumps({"error": str(e)}, ensure_ascii=False))
|
|
328
|
+
raise click.ClickException(str(e))
|
|
329
|
+
out(result)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from cloudrobo_core.sdk import BaseClient
|
|
4
|
+
from cloudrobo_core.sdk.exceptions import validate_safe_id
|
|
5
|
+
|
|
6
|
+
from .validators import validate_params
|
|
7
|
+
|
|
8
|
+
TERMINAL_STATES = {"FAILED", "RUNNING", "STOPPING", "STOPPED", "DELETING", "ERROR"}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InferClient(BaseClient):
|
|
12
|
+
SERVICE = "cloudrobo-service"
|
|
13
|
+
|
|
14
|
+
@validate_params("create_infer_service")
|
|
15
|
+
def create_infer_service(self, req: dict) -> dict:
|
|
16
|
+
return self._client.post(self._url("/v1/infer-services"), json=req)
|
|
17
|
+
|
|
18
|
+
def list_infer_services(self, **params) -> dict:
|
|
19
|
+
return self._client.get(self._url("/v1/infer-services"), params=params)
|
|
20
|
+
|
|
21
|
+
def show_infer_service(self, service_id: str) -> dict:
|
|
22
|
+
validate_safe_id(service_id, "service_id")
|
|
23
|
+
return self._client.get(self._url(f"/v1/infer-services/{service_id}"))
|
|
24
|
+
|
|
25
|
+
@validate_params("update_infer_service")
|
|
26
|
+
def update_infer_service(self, service_id: str, req: dict) -> dict:
|
|
27
|
+
validate_safe_id(service_id, "service_id")
|
|
28
|
+
return self._client.put(self._url(f"/v1/infer-services/{service_id}"), json=req)
|
|
29
|
+
|
|
30
|
+
def delete_infer_service(self, service_id: str) -> dict:
|
|
31
|
+
validate_safe_id(service_id, "service_id")
|
|
32
|
+
return self._client.delete(self._url(f"/v1/infer-services/{service_id}"))
|
|
33
|
+
|
|
34
|
+
def start_infer_service(self, service_id: str) -> dict:
|
|
35
|
+
validate_safe_id(service_id, "service_id")
|
|
36
|
+
return self._client.post(self._url(f"/v1/infer-services/{service_id}/start"))
|
|
37
|
+
|
|
38
|
+
def stop_infer_service(self, service_id: str) -> dict:
|
|
39
|
+
validate_safe_id(service_id, "service_id")
|
|
40
|
+
return self._client.post(self._url(f"/v1/infer-services/{service_id}/stop"))
|
|
41
|
+
|
|
42
|
+
@validate_params("list_infer_service_logs")
|
|
43
|
+
def list_infer_service_logs(self, service_id: str, req: dict) -> dict:
|
|
44
|
+
validate_safe_id(service_id, "service_id")
|
|
45
|
+
return self._client.post(self._url(f"/v1/infer-services/{service_id}/logs"), json=req)
|
|
46
|
+
|
|
47
|
+
def wait_deploy(self, service_id: str, timeout: int = 600) -> dict:
|
|
48
|
+
validate_safe_id(service_id, "service_id")
|
|
49
|
+
elapsed = 0
|
|
50
|
+
while elapsed < timeout:
|
|
51
|
+
service = self.show_infer_service(service_id)
|
|
52
|
+
if service.get("status") != "DEPLOYING":
|
|
53
|
+
return service
|
|
54
|
+
time.sleep(5)
|
|
55
|
+
elapsed += 5
|
|
56
|
+
last = self.show_infer_service(service_id)
|
|
57
|
+
raise RuntimeError(
|
|
58
|
+
f"wait-deploy timeout after {timeout}s, last status: {last.get('status')}"
|
|
59
|
+
)
|