agentscope-runtime 1.0.1__py3-none-any.whl → 1.0.2__py3-none-any.whl
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.
- agentscope_runtime/adapters/agentscope/message.py +32 -7
- agentscope_runtime/adapters/agentscope/stream.py +121 -91
- agentscope_runtime/adapters/agno/__init__.py +0 -0
- agentscope_runtime/adapters/agno/message.py +30 -0
- agentscope_runtime/adapters/agno/stream.py +122 -0
- agentscope_runtime/adapters/langgraph/__init__.py +12 -0
- agentscope_runtime/adapters/langgraph/message.py +257 -0
- agentscope_runtime/adapters/langgraph/stream.py +205 -0
- agentscope_runtime/cli/__init__.py +7 -0
- agentscope_runtime/cli/cli.py +63 -0
- agentscope_runtime/cli/commands/__init__.py +2 -0
- agentscope_runtime/cli/commands/chat.py +815 -0
- agentscope_runtime/cli/commands/deploy.py +1062 -0
- agentscope_runtime/cli/commands/invoke.py +58 -0
- agentscope_runtime/cli/commands/list_cmd.py +103 -0
- agentscope_runtime/cli/commands/run.py +176 -0
- agentscope_runtime/cli/commands/sandbox.py +128 -0
- agentscope_runtime/cli/commands/status.py +60 -0
- agentscope_runtime/cli/commands/stop.py +185 -0
- agentscope_runtime/cli/commands/web.py +166 -0
- agentscope_runtime/cli/loaders/__init__.py +6 -0
- agentscope_runtime/cli/loaders/agent_loader.py +295 -0
- agentscope_runtime/cli/state/__init__.py +10 -0
- agentscope_runtime/cli/utils/__init__.py +18 -0
- agentscope_runtime/cli/utils/console.py +378 -0
- agentscope_runtime/cli/utils/validators.py +118 -0
- agentscope_runtime/engine/app/agent_app.py +7 -4
- agentscope_runtime/engine/deployers/__init__.py +1 -0
- agentscope_runtime/engine/deployers/agentrun_deployer.py +152 -22
- agentscope_runtime/engine/deployers/base.py +27 -2
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +158 -31
- agentscope_runtime/engine/deployers/local_deployer.py +188 -25
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +109 -18
- agentscope_runtime/engine/deployers/state/__init__.py +9 -0
- agentscope_runtime/engine/deployers/state/manager.py +388 -0
- agentscope_runtime/engine/deployers/state/schema.py +96 -0
- agentscope_runtime/engine/deployers/utils/build_cache.py +736 -0
- agentscope_runtime/engine/deployers/utils/detached_app.py +105 -30
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +31 -10
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +15 -8
- agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +30 -2
- agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
- agentscope_runtime/engine/deployers/utils/package.py +56 -6
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +16 -2
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +155 -5
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +107 -123
- agentscope_runtime/engine/runner.py +25 -6
- agentscope_runtime/engine/schemas/exception.py +580 -0
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
- agentscope_runtime/sandbox/utils.py +2 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/METADATA +24 -7
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/RECORD +58 -28
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
# pylint:disable=protected-access, unused-argument
|
|
2
|
+
# pylint:disable=protected-access, unused-argument, too-many-branches
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
import socket
|
|
8
8
|
import threading
|
|
9
|
+
from datetime import datetime
|
|
9
10
|
from typing import Callable, Optional, Type, Any, Dict, Union, List
|
|
10
11
|
|
|
11
12
|
import uvicorn
|
|
12
13
|
|
|
14
|
+
from agentscope_runtime.engine.deployers.state import Deployment
|
|
13
15
|
from .adapter.protocol_adapter import ProtocolAdapter
|
|
14
16
|
from .base import DeployManager
|
|
15
17
|
from .utils.deployment_modes import DeploymentMode
|
|
@@ -29,7 +31,7 @@ class LocalDeployManager(DeployManager):
|
|
|
29
31
|
def __init__(
|
|
30
32
|
self,
|
|
31
33
|
host: str = "127.0.0.1",
|
|
32
|
-
port: int =
|
|
34
|
+
port: int = 8090,
|
|
33
35
|
shutdown_timeout: int = 30,
|
|
34
36
|
startup_timeout: int = 30,
|
|
35
37
|
logger: Optional[logging.Logger] = None,
|
|
@@ -80,6 +82,9 @@ class LocalDeployManager(DeployManager):
|
|
|
80
82
|
broker_url: Optional[str] = None,
|
|
81
83
|
backend_url: Optional[str] = None,
|
|
82
84
|
enable_embedded_worker: bool = False,
|
|
85
|
+
# New parameters for project-based deployment
|
|
86
|
+
project_dir: Optional[str] = None,
|
|
87
|
+
entrypoint: Optional[str] = None,
|
|
83
88
|
**kwargs: Any,
|
|
84
89
|
) -> Dict[str, str]:
|
|
85
90
|
"""Deploy using unified FastAPI architecture.
|
|
@@ -100,6 +105,8 @@ class LocalDeployManager(DeployManager):
|
|
|
100
105
|
backend_url: Celery backend URL for result storage
|
|
101
106
|
enable_embedded_worker: Whether to run Celery worker
|
|
102
107
|
embedded in the app
|
|
108
|
+
project_dir: Project directory (for DETACHED_PROCESS mode)
|
|
109
|
+
entrypoint: Entrypoint specification (for DETACHED_PROCESS mode)
|
|
103
110
|
**kwargs: Additional keyword arguments
|
|
104
111
|
|
|
105
112
|
Returns:
|
|
@@ -152,6 +159,8 @@ class LocalDeployManager(DeployManager):
|
|
|
152
159
|
after_finish=after_finish,
|
|
153
160
|
custom_endpoints=custom_endpoints,
|
|
154
161
|
protocol_adapters=protocol_adapters,
|
|
162
|
+
project_dir=project_dir,
|
|
163
|
+
entrypoint=entrypoint,
|
|
155
164
|
**kwargs,
|
|
156
165
|
)
|
|
157
166
|
else:
|
|
@@ -171,6 +180,7 @@ class LocalDeployManager(DeployManager):
|
|
|
171
180
|
broker_url: Optional[str] = None,
|
|
172
181
|
backend_url: Optional[str] = None,
|
|
173
182
|
enable_embedded_worker: bool = False,
|
|
183
|
+
agent_source: Optional[str] = None,
|
|
174
184
|
**kwargs,
|
|
175
185
|
) -> Dict[str, str]:
|
|
176
186
|
"""Deploy in daemon thread mode."""
|
|
@@ -208,21 +218,43 @@ class LocalDeployManager(DeployManager):
|
|
|
208
218
|
await self._wait_for_server_ready(self._startup_timeout)
|
|
209
219
|
|
|
210
220
|
self.is_running = True
|
|
211
|
-
|
|
221
|
+
|
|
222
|
+
url = f"http://{self.host}:{self.port}"
|
|
212
223
|
|
|
213
224
|
self._logger.info(
|
|
214
|
-
f"FastAPI service started at
|
|
225
|
+
f"FastAPI service started at {url}",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
deployment = Deployment(
|
|
229
|
+
id=self.deploy_id,
|
|
230
|
+
platform="local",
|
|
231
|
+
url=url,
|
|
232
|
+
status="running",
|
|
233
|
+
created_at=datetime.now().isoformat(),
|
|
234
|
+
agent_source=agent_source,
|
|
235
|
+
config={
|
|
236
|
+
"mode": DeploymentMode.DAEMON_THREAD,
|
|
237
|
+
"host": self.host,
|
|
238
|
+
"port": self.port,
|
|
239
|
+
"broker_url": broker_url,
|
|
240
|
+
"backend_url": backend_url,
|
|
241
|
+
"enable_embedded_worker": enable_embedded_worker,
|
|
242
|
+
},
|
|
215
243
|
)
|
|
244
|
+
self.state_manager.save(deployment)
|
|
216
245
|
|
|
217
246
|
return {
|
|
218
247
|
"deploy_id": self.deploy_id,
|
|
219
|
-
"url":
|
|
248
|
+
"url": url,
|
|
220
249
|
}
|
|
221
250
|
|
|
222
251
|
async def _deploy_detached_process(
|
|
223
252
|
self,
|
|
224
253
|
runner: Optional[Any] = None,
|
|
225
254
|
protocol_adapters: Optional[list[ProtocolAdapter]] = None,
|
|
255
|
+
project_dir: Optional[str] = None,
|
|
256
|
+
entrypoint: Optional[str] = None,
|
|
257
|
+
agent_source: Optional[str] = None,
|
|
226
258
|
**kwargs,
|
|
227
259
|
) -> Dict[str, str]:
|
|
228
260
|
"""Deploy in detached process mode."""
|
|
@@ -230,9 +262,14 @@ class LocalDeployManager(DeployManager):
|
|
|
230
262
|
"Deploying FastAPI service in detached process mode...",
|
|
231
263
|
)
|
|
232
264
|
|
|
233
|
-
|
|
265
|
+
# Clean up old log files (older than 24 hours)
|
|
266
|
+
ProcessManager.cleanup_old_logs(max_age_hours=24)
|
|
267
|
+
|
|
268
|
+
# Original behavior: require app or runner or entrypoint
|
|
269
|
+
if runner is None and self._app is None and entrypoint is None:
|
|
234
270
|
raise ValueError(
|
|
235
|
-
"Detached process mode requires an app
|
|
271
|
+
"Detached process mode requires an app, runner, "
|
|
272
|
+
"project_dir, or entrypoint",
|
|
236
273
|
)
|
|
237
274
|
|
|
238
275
|
if "agent" in kwargs:
|
|
@@ -245,18 +282,29 @@ class LocalDeployManager(DeployManager):
|
|
|
245
282
|
app=self._app,
|
|
246
283
|
runner=runner,
|
|
247
284
|
protocol_adapters=protocol_adapters,
|
|
285
|
+
entrypoint=entrypoint,
|
|
248
286
|
**kwargs,
|
|
249
287
|
)
|
|
250
288
|
|
|
289
|
+
if not project_dir:
|
|
290
|
+
raise RuntimeError("Failed to parse project directory")
|
|
291
|
+
|
|
251
292
|
try:
|
|
252
293
|
entry_script = get_bundle_entry_script(project_dir)
|
|
253
294
|
script_path = os.path.join(project_dir, entry_script)
|
|
254
|
-
|
|
295
|
+
env = kwargs.get("environment", {}) or {}
|
|
296
|
+
env.update(
|
|
297
|
+
{
|
|
298
|
+
"HOST": self.host,
|
|
299
|
+
"PORT": str(self.port),
|
|
300
|
+
},
|
|
301
|
+
)
|
|
255
302
|
# Start detached process using the packaged project
|
|
256
303
|
pid = await self.process_manager.start_detached_process(
|
|
257
304
|
script_path=script_path,
|
|
258
305
|
host=self.host,
|
|
259
306
|
port=self.port,
|
|
307
|
+
env=env,
|
|
260
308
|
)
|
|
261
309
|
|
|
262
310
|
self._detached_process_pid = pid
|
|
@@ -273,35 +321,65 @@ class LocalDeployManager(DeployManager):
|
|
|
273
321
|
)
|
|
274
322
|
|
|
275
323
|
if not service_ready:
|
|
276
|
-
|
|
324
|
+
# Check if process is still running
|
|
325
|
+
is_running = self.process_manager.is_process_running(pid)
|
|
326
|
+
|
|
327
|
+
# Get process logs
|
|
328
|
+
logs = self.process_manager.get_process_logs(max_lines=50)
|
|
329
|
+
|
|
330
|
+
# Log the detailed error for debugging
|
|
331
|
+
self._logger.error(
|
|
332
|
+
f"Service did not start within timeout. "
|
|
333
|
+
f"Process (PID: {pid}) status: "
|
|
334
|
+
f"{'running' if is_running else 'terminated'}. "
|
|
335
|
+
f"Host: {self.host}, Port: {self.port}.\n\n"
|
|
336
|
+
f"Process logs:\n{logs}",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Raise a simple error message
|
|
340
|
+
raise RuntimeError(
|
|
341
|
+
"Service failed to start. Check logs above for details.",
|
|
342
|
+
)
|
|
277
343
|
|
|
278
344
|
self.is_running = True
|
|
279
|
-
|
|
345
|
+
|
|
346
|
+
url = f"http://{self.host}:{self.port}"
|
|
280
347
|
|
|
281
348
|
self._logger.info(
|
|
282
349
|
f"FastAPI service started in detached process (PID: {pid})",
|
|
283
350
|
)
|
|
284
351
|
|
|
352
|
+
deployment = Deployment(
|
|
353
|
+
id=self.deploy_id,
|
|
354
|
+
platform="local",
|
|
355
|
+
url=url,
|
|
356
|
+
status="running",
|
|
357
|
+
created_at=datetime.now().isoformat(),
|
|
358
|
+
agent_source=agent_source,
|
|
359
|
+
config={
|
|
360
|
+
"mode": DeploymentMode.DETACHED_PROCESS,
|
|
361
|
+
"host": self.host,
|
|
362
|
+
"port": self.port,
|
|
363
|
+
"pid": pid,
|
|
364
|
+
"pid_file": self._detached_pid_file,
|
|
365
|
+
"project_dir": project_dir,
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
self.state_manager.save(deployment)
|
|
369
|
+
|
|
285
370
|
return {
|
|
286
371
|
"deploy_id": self.deploy_id,
|
|
287
|
-
"url":
|
|
372
|
+
"url": url,
|
|
288
373
|
}
|
|
289
374
|
|
|
290
375
|
except Exception as e:
|
|
291
|
-
# Cleanup on failure
|
|
292
|
-
if os.path.exists(project_dir):
|
|
293
|
-
try:
|
|
294
|
-
import shutil
|
|
295
|
-
|
|
296
|
-
shutil.rmtree(project_dir)
|
|
297
|
-
except OSError:
|
|
298
|
-
pass
|
|
299
376
|
raise e
|
|
300
377
|
|
|
301
378
|
@staticmethod
|
|
302
379
|
async def create_detached_project(
|
|
303
380
|
app=None,
|
|
304
381
|
runner: Optional[Any] = None,
|
|
382
|
+
entrypoint: Optional[str] = None,
|
|
305
383
|
endpoint_path: str = "/process",
|
|
306
384
|
requirements: Optional[Union[str, List[str]]] = None,
|
|
307
385
|
extra_packages: Optional[List[str]] = None,
|
|
@@ -310,6 +388,7 @@ class LocalDeployManager(DeployManager):
|
|
|
310
388
|
broker_url: Optional[str] = None,
|
|
311
389
|
backend_url: Optional[str] = None,
|
|
312
390
|
enable_embedded_worker: bool = False,
|
|
391
|
+
platform: str = "local",
|
|
313
392
|
**kwargs,
|
|
314
393
|
) -> str:
|
|
315
394
|
project_dir, _ = build_detached_app(
|
|
@@ -317,18 +396,81 @@ class LocalDeployManager(DeployManager):
|
|
|
317
396
|
runner=runner,
|
|
318
397
|
requirements=requirements,
|
|
319
398
|
extra_packages=extra_packages,
|
|
399
|
+
platform=platform,
|
|
400
|
+
entrypoint=entrypoint,
|
|
320
401
|
**kwargs,
|
|
321
402
|
)
|
|
322
403
|
|
|
323
404
|
return project_dir
|
|
324
405
|
|
|
325
|
-
async def stop(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
406
|
+
async def stop(
|
|
407
|
+
self,
|
|
408
|
+
deploy_id: str,
|
|
409
|
+
**kwargs,
|
|
410
|
+
) -> Dict[str, Any]:
|
|
411
|
+
"""Stop the FastAPI service.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
deploy_id: Deployment identifier
|
|
415
|
+
**kwargs: Additional parameters
|
|
330
416
|
|
|
417
|
+
Returns:
|
|
418
|
+
Dict with success status, message, and details
|
|
419
|
+
"""
|
|
420
|
+
# If URL not provided, try to get it from state manager
|
|
331
421
|
try:
|
|
422
|
+
deployment = self.state_manager.get(deploy_id)
|
|
423
|
+
if deployment:
|
|
424
|
+
url = deployment.url
|
|
425
|
+
self._logger.debug(f"Fetched URL from state: {url}")
|
|
426
|
+
except Exception as e:
|
|
427
|
+
self._logger.debug(f"Could not fetch URL from state: {e}")
|
|
428
|
+
|
|
429
|
+
if not deployment:
|
|
430
|
+
return {
|
|
431
|
+
"success": False,
|
|
432
|
+
"message": "Deploy id not found",
|
|
433
|
+
"details": {
|
|
434
|
+
"deploy_id": deploy_id,
|
|
435
|
+
"error": "Deploy id not found",
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
# Only attempt HTTP shutdown for detached process mode
|
|
440
|
+
# In daemon thread mode, HTTP shutdown would kill the entire process
|
|
441
|
+
# (including pytest), so we skip it and use direct stop methods instead
|
|
442
|
+
if (
|
|
443
|
+
url
|
|
444
|
+
and deployment.config["mode"] == DeploymentMode.DETACHED_PROCESS
|
|
445
|
+
):
|
|
446
|
+
try:
|
|
447
|
+
import requests
|
|
448
|
+
|
|
449
|
+
response = requests.post(f"{url}/shutdown", timeout=5)
|
|
450
|
+
if response.status_code == 200:
|
|
451
|
+
# Remove from state manager on successful shutdown
|
|
452
|
+
try:
|
|
453
|
+
self.state_manager.update_status(deploy_id, "stopped")
|
|
454
|
+
except KeyError:
|
|
455
|
+
self._logger.debug(
|
|
456
|
+
f"Deployment {deploy_id} not found "
|
|
457
|
+
f"in state (already removed)",
|
|
458
|
+
)
|
|
459
|
+
self.is_running = False
|
|
460
|
+
return {
|
|
461
|
+
"success": True,
|
|
462
|
+
"message": "Shutdown signal sent to detached process",
|
|
463
|
+
"details": {"url": url, "deploy_id": deploy_id},
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
except requests.exceptions.RequestException as e:
|
|
467
|
+
# If HTTP shutdown fails, continue with direct stop methods
|
|
468
|
+
self._logger.debug(
|
|
469
|
+
f"HTTP shutdown failed, falling back to direct stop: {e}",
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
# when run in from main process instead of cli, make sure close
|
|
332
474
|
if self._detached_process_pid:
|
|
333
475
|
# Detached process mode
|
|
334
476
|
await self._stop_detached_process()
|
|
@@ -336,9 +478,27 @@ class LocalDeployManager(DeployManager):
|
|
|
336
478
|
# Daemon thread mode
|
|
337
479
|
await self._stop_daemon_thread()
|
|
338
480
|
|
|
481
|
+
# Remove from state manager on successful stop
|
|
482
|
+
try:
|
|
483
|
+
self.state_manager.update_status(deploy_id, "stopped")
|
|
484
|
+
except KeyError:
|
|
485
|
+
self._logger.debug(
|
|
486
|
+
f"Deployment {deploy_id} not found in state (already "
|
|
487
|
+
f"removed)",
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
"success": True,
|
|
492
|
+
"message": "Service stopped successfully",
|
|
493
|
+
"details": {"deploy_id": deploy_id},
|
|
494
|
+
}
|
|
339
495
|
except Exception as e:
|
|
340
496
|
self._logger.error(f"Failed to stop service: {e}")
|
|
341
|
-
|
|
497
|
+
return {
|
|
498
|
+
"success": False,
|
|
499
|
+
"message": f"Failed to stop service: {e}",
|
|
500
|
+
"details": {"deploy_id": deploy_id, "error": str(e)},
|
|
501
|
+
}
|
|
342
502
|
|
|
343
503
|
async def _stop_daemon_thread(self):
|
|
344
504
|
"""Stop daemon thread mode service."""
|
|
@@ -387,6 +547,9 @@ class LocalDeployManager(DeployManager):
|
|
|
387
547
|
if self._detached_pid_file:
|
|
388
548
|
self.process_manager.cleanup_pid_file(self._detached_pid_file)
|
|
389
549
|
|
|
550
|
+
# Cleanup log file (keep file for debugging)
|
|
551
|
+
self.process_manager.cleanup_log_file(keep_file=True)
|
|
552
|
+
|
|
390
553
|
# Reset state
|
|
391
554
|
self._detached_process_pid = None
|
|
392
555
|
self._detached_pid_file = None
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
-
import
|
|
10
|
+
from datetime import datetime
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Dict, Optional, List, Union, Tuple
|
|
12
|
+
from typing import Dict, Optional, List, Union, Tuple, Any
|
|
13
13
|
|
|
14
14
|
import requests
|
|
15
15
|
from pydantic import BaseModel, Field
|
|
@@ -17,11 +17,14 @@ from pydantic import BaseModel, Field
|
|
|
17
17
|
from .adapter.protocol_adapter import ProtocolAdapter
|
|
18
18
|
from .base import DeployManager
|
|
19
19
|
from .local_deployer import LocalDeployManager
|
|
20
|
+
from .state import Deployment
|
|
20
21
|
from .utils.detached_app import get_bundle_entry_script
|
|
22
|
+
from .utils.package import generate_build_directory
|
|
21
23
|
from .utils.wheel_packager import (
|
|
22
24
|
generate_wrapper_project,
|
|
23
25
|
build_wheel,
|
|
24
26
|
default_deploy_name,
|
|
27
|
+
get_user_bundle_appdir,
|
|
25
28
|
)
|
|
26
29
|
|
|
27
30
|
logger = logging.getLogger(__name__)
|
|
@@ -538,8 +541,9 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
538
541
|
oss_config: Optional[OSSConfig] = None,
|
|
539
542
|
modelstudio_config: Optional[ModelstudioConfig] = None,
|
|
540
543
|
build_root: Optional[Union[str, Path]] = None,
|
|
544
|
+
state_manager=None,
|
|
541
545
|
) -> None:
|
|
542
|
-
super().__init__()
|
|
546
|
+
super().__init__(state_manager=state_manager)
|
|
543
547
|
self.oss_config = oss_config or OSSConfig.from_env()
|
|
544
548
|
self.modelstudio_config = (
|
|
545
549
|
modelstudio_config or ModelstudioConfig.from_env()
|
|
@@ -552,11 +556,11 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
552
556
|
cmd: Optional[str] = None,
|
|
553
557
|
deploy_name: Optional[str] = None,
|
|
554
558
|
telemetry_enabled: bool = True,
|
|
559
|
+
environment: Optional[Dict[str, str]] = None,
|
|
560
|
+
requirements: Optional[Union[str, List[str]]] = None,
|
|
555
561
|
) -> Tuple[Path, str]:
|
|
556
562
|
"""
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
返回: (wheel_path, wrapper_project_dir, name)
|
|
563
|
+
generate temp project path and build wheel.
|
|
560
564
|
"""
|
|
561
565
|
if not project_dir or not cmd:
|
|
562
566
|
raise ValueError(
|
|
@@ -569,18 +573,18 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
569
573
|
raise FileNotFoundError(f"Project dir not found: {project_dir}")
|
|
570
574
|
|
|
571
575
|
name = deploy_name or default_deploy_name()
|
|
572
|
-
|
|
576
|
+
|
|
577
|
+
# Generate build directory with platform-aware naming
|
|
573
578
|
if isinstance(self.build_root, Path):
|
|
574
579
|
effective_build_root = self.build_root.resolve()
|
|
575
580
|
else:
|
|
576
581
|
if self.build_root:
|
|
577
582
|
effective_build_root = Path(self.build_root).resolve()
|
|
578
583
|
else:
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
).resolve()
|
|
584
|
+
# Use centralized directory generation function
|
|
585
|
+
effective_build_root = generate_build_directory("modelstudio")
|
|
582
586
|
|
|
583
|
-
build_dir = effective_build_root
|
|
587
|
+
build_dir = effective_build_root
|
|
584
588
|
build_dir.mkdir(parents=True, exist_ok=True)
|
|
585
589
|
|
|
586
590
|
logger.info("Generating wrapper project for %s", name)
|
|
@@ -590,10 +594,15 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
590
594
|
start_cmd=cmd,
|
|
591
595
|
deploy_name=name,
|
|
592
596
|
telemetry_enabled=telemetry_enabled,
|
|
597
|
+
requirements=requirements,
|
|
593
598
|
)
|
|
594
599
|
|
|
600
|
+
# pass environments to the project from user setting
|
|
601
|
+
user_bundle_app_dir = get_user_bundle_appdir(build_dir, project_dir)
|
|
602
|
+
self._generate_env_file(user_bundle_app_dir, environment)
|
|
595
603
|
logger.info("Building wheel under %s", wrapper_project_dir)
|
|
596
604
|
wheel_path = build_wheel(wrapper_project_dir)
|
|
605
|
+
|
|
597
606
|
return wheel_path, name
|
|
598
607
|
|
|
599
608
|
def _generate_env_file(
|
|
@@ -616,7 +625,9 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
616
625
|
variables provided
|
|
617
626
|
"""
|
|
618
627
|
if not environment:
|
|
619
|
-
|
|
628
|
+
environment = {}
|
|
629
|
+
environment["HOST"] = os.environ.get("HOST", "0.0.0.0")
|
|
630
|
+
environment["PORT"] = int(os.environ.get("PORT", "8080"))
|
|
620
631
|
|
|
621
632
|
project_path = Path(project_dir).resolve()
|
|
622
633
|
if not project_path.exists():
|
|
@@ -731,14 +742,19 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
731
742
|
resource_name (deploy_name), and workspace_id.
|
|
732
743
|
"""
|
|
733
744
|
if not agent_id:
|
|
734
|
-
if
|
|
745
|
+
if (
|
|
746
|
+
not app
|
|
747
|
+
and not runner
|
|
748
|
+
and not project_dir
|
|
749
|
+
and not external_whl_path
|
|
750
|
+
):
|
|
735
751
|
raise ValueError(
|
|
736
|
-
"Either runner, project_dir, "
|
|
752
|
+
"Either app, runner, project_dir, "
|
|
737
753
|
"or external_whl_path must be provided.",
|
|
738
754
|
)
|
|
739
755
|
|
|
740
756
|
try:
|
|
741
|
-
if runner:
|
|
757
|
+
if runner or app:
|
|
742
758
|
if "agent" in kwargs:
|
|
743
759
|
kwargs.pop("agent")
|
|
744
760
|
|
|
@@ -751,6 +767,7 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
751
767
|
requirements=requirements,
|
|
752
768
|
extra_packages=extra_packages,
|
|
753
769
|
port=8080,
|
|
770
|
+
platform="modelstudio",
|
|
754
771
|
**kwargs,
|
|
755
772
|
)
|
|
756
773
|
if project_dir:
|
|
@@ -793,6 +810,8 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
793
810
|
cmd=cmd,
|
|
794
811
|
deploy_name=deploy_name,
|
|
795
812
|
telemetry_enabled=telemetry_enabled,
|
|
813
|
+
environment=environment,
|
|
814
|
+
requirements=requirements,
|
|
796
815
|
)
|
|
797
816
|
|
|
798
817
|
console_url = ""
|
|
@@ -814,16 +833,41 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
814
833
|
telemetry_enabled,
|
|
815
834
|
)
|
|
816
835
|
|
|
836
|
+
# Use base class UUID deploy_id (already set in __init__)
|
|
837
|
+
deploy_id = self.deploy_id
|
|
838
|
+
|
|
839
|
+
# Save deployment to state manager
|
|
840
|
+
if deploy_identifier:
|
|
841
|
+
deployment = Deployment(
|
|
842
|
+
id=deploy_id,
|
|
843
|
+
platform="modelstudio",
|
|
844
|
+
url=console_url,
|
|
845
|
+
status="running",
|
|
846
|
+
created_at=datetime.now().isoformat(),
|
|
847
|
+
agent_source=kwargs.get("agent_source"),
|
|
848
|
+
config={
|
|
849
|
+
"modelstudio_deploy_id": deploy_identifier,
|
|
850
|
+
"resource_name": name,
|
|
851
|
+
"workspace_id": os.environ.get(
|
|
852
|
+
"MODELSTUDIO_WORKSPACE_ID",
|
|
853
|
+
"",
|
|
854
|
+
).strip(),
|
|
855
|
+
"wheel_path": str(wheel_path),
|
|
856
|
+
},
|
|
857
|
+
)
|
|
858
|
+
self.state_manager.save(deployment)
|
|
859
|
+
|
|
817
860
|
result: Dict[str, str] = {
|
|
818
861
|
"wheel_path": str(wheel_path),
|
|
819
862
|
"resource_name": name,
|
|
820
863
|
"url": console_url,
|
|
864
|
+
"deploy_id": deploy_id,
|
|
821
865
|
}
|
|
822
866
|
env_ws = os.environ.get("MODELSTUDIO_WORKSPACE_ID")
|
|
823
867
|
if env_ws and env_ws.strip():
|
|
824
868
|
result["workspace_id"] = env_ws.strip()
|
|
825
869
|
if deploy_identifier:
|
|
826
|
-
result["
|
|
870
|
+
result["modelstudio_deploy_id"] = deploy_identifier
|
|
827
871
|
|
|
828
872
|
return result
|
|
829
873
|
except Exception as e:
|
|
@@ -835,8 +879,55 @@ class ModelstudioDeployManager(DeployManager):
|
|
|
835
879
|
)
|
|
836
880
|
raise
|
|
837
881
|
|
|
838
|
-
async def stop(self
|
|
839
|
-
|
|
882
|
+
async def stop(self, deploy_id: str, **kwargs) -> Dict[str, Any]:
|
|
883
|
+
"""Stop ModelStudio deployment.
|
|
884
|
+
|
|
885
|
+
Note: ModelStudio stop API not yet available.
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
deploy_id: Deployment identifier
|
|
889
|
+
**kwargs: Additional parameters
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
Dict with success status and message
|
|
893
|
+
"""
|
|
894
|
+
# Try to get deployment info from state for context
|
|
895
|
+
deployment_info = None
|
|
896
|
+
try:
|
|
897
|
+
deployment = self.state_manager.get(deploy_id)
|
|
898
|
+
if deployment:
|
|
899
|
+
deployment_info = {
|
|
900
|
+
"url": deployment.url
|
|
901
|
+
if hasattr(deployment, "url")
|
|
902
|
+
else None,
|
|
903
|
+
"workspace_id": getattr(deployment, "workspace_id", None),
|
|
904
|
+
}
|
|
905
|
+
logger.debug(
|
|
906
|
+
f"Fetched deployment info from state: {deployment_info}",
|
|
907
|
+
)
|
|
908
|
+
except Exception as e:
|
|
909
|
+
logger.debug(f"Could not fetch deployment info from state: {e}")
|
|
910
|
+
|
|
911
|
+
# TODO: Implement when ModelStudio provides stop/delete API
|
|
912
|
+
# When API is available, call it here and then remove from state:
|
|
913
|
+
# self.state_manager.remove(deploy_id)
|
|
914
|
+
|
|
915
|
+
logger.warning(
|
|
916
|
+
f"ModelStudio stop not implemented for deploy_id={deploy_id} - API not yet available",
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
details = {
|
|
920
|
+
"deploy_id": deploy_id,
|
|
921
|
+
"note": "Manual cleanup required via ModelStudio console",
|
|
922
|
+
}
|
|
923
|
+
if deployment_info:
|
|
924
|
+
details.update(deployment_info)
|
|
925
|
+
|
|
926
|
+
return {
|
|
927
|
+
"success": False,
|
|
928
|
+
"message": "ModelStudio stop not implemented - API not yet available",
|
|
929
|
+
"details": details,
|
|
930
|
+
}
|
|
840
931
|
|
|
841
932
|
def get_status(self) -> str: # pragma: no cover - not supported yet
|
|
842
933
|
return "unknown"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Deployment state management."""
|
|
3
|
+
|
|
4
|
+
from agentscope_runtime.engine.deployers.state.manager import (
|
|
5
|
+
DeploymentStateManager,
|
|
6
|
+
)
|
|
7
|
+
from agentscope_runtime.engine.deployers.state.schema import Deployment
|
|
8
|
+
|
|
9
|
+
__all__ = ["DeploymentStateManager", "Deployment"]
|