agentscope-runtime 1.0.1__py3-none-any.whl → 1.0.3__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 +1074 -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/common/collections/redis_mapping.py +4 -1
- agentscope_runtime/engine/app/agent_app.py +55 -9
- agentscope_runtime/engine/deployers/__init__.py +1 -0
- agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +56 -1
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +449 -41
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +273 -0
- agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +640 -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 +161 -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 +23 -10
- agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +35 -2
- agentscope_runtime/engine/deployers/utils/k8s_utils.py +241 -0
- agentscope_runtime/engine/deployers/utils/net_utils.py +65 -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 +30 -9
- agentscope_runtime/engine/schemas/exception.py +604 -0
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
- agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
- agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
- agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +113 -39
- agentscope_runtime/sandbox/box/shared/routers/mcp_utils.py +20 -4
- agentscope_runtime/sandbox/build.py +50 -57
- agentscope_runtime/sandbox/utils.py +2 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/METADATA +31 -8
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/RECORD +69 -36
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/WHEEL +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-1.0.1.dist-info → agentscope_runtime-1.0.3.dist-info}/top_level.txt +0 -0
|
@@ -7,6 +7,7 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
import time
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Dict, Optional, List, Any, Union, Tuple
|
|
12
13
|
|
|
@@ -34,7 +35,9 @@ from pydantic import BaseModel, Field
|
|
|
34
35
|
from .adapter.protocol_adapter import ProtocolAdapter
|
|
35
36
|
from .base import DeployManager
|
|
36
37
|
from .local_deployer import LocalDeployManager
|
|
38
|
+
from .state import Deployment
|
|
37
39
|
from .utils.detached_app import get_bundle_entry_script
|
|
40
|
+
from .utils.package import generate_build_directory
|
|
38
41
|
from .utils.wheel_packager import (
|
|
39
42
|
default_deploy_name,
|
|
40
43
|
generate_wrapper_project,
|
|
@@ -95,7 +98,7 @@ class AgentRunConfig(BaseModel):
|
|
|
95
98
|
|
|
96
99
|
execution_role_arn: Optional[str] = None
|
|
97
100
|
|
|
98
|
-
session_concurrency_limit: Optional[int] =
|
|
101
|
+
session_concurrency_limit: Optional[int] = 200
|
|
99
102
|
session_idle_timeout_seconds: Optional[int] = 3600
|
|
100
103
|
|
|
101
104
|
@classmethod
|
|
@@ -147,7 +150,7 @@ class AgentRunConfig(BaseModel):
|
|
|
147
150
|
|
|
148
151
|
session_concurrency_limit_str = os.environ.get(
|
|
149
152
|
"AGENT_RUN_SESSION_CONCURRENCY_LIMIT",
|
|
150
|
-
"
|
|
153
|
+
"200",
|
|
151
154
|
)
|
|
152
155
|
session_idle_timeout_seconds_str = os.environ.get(
|
|
153
156
|
"AGENT_RUN_SESSION_IDLE_TIMEOUT_SECONDS",
|
|
@@ -218,6 +221,7 @@ class OSSConfig(BaseModel):
|
|
|
218
221
|
region: str = Field("cn-hangzhou", description="OSS region")
|
|
219
222
|
access_key_id: Optional[str] = None
|
|
220
223
|
access_key_secret: Optional[str] = None
|
|
224
|
+
bucket_name: str
|
|
221
225
|
|
|
222
226
|
@classmethod
|
|
223
227
|
def from_env(cls) -> "OSSConfig":
|
|
@@ -236,6 +240,7 @@ class OSSConfig(BaseModel):
|
|
|
236
240
|
"OSS_ACCESS_KEY_SECRET",
|
|
237
241
|
os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET"),
|
|
238
242
|
),
|
|
243
|
+
bucket_name=os.environ.get("OSS_BUCKET_NAME"),
|
|
239
244
|
)
|
|
240
245
|
|
|
241
246
|
def ensure_valid(self) -> None:
|
|
@@ -245,10 +250,14 @@ class OSSConfig(BaseModel):
|
|
|
245
250
|
RuntimeError: If required AccessKey credentials are missing.
|
|
246
251
|
"""
|
|
247
252
|
# Allow fallback to Alibaba Cloud AK/SK via from_env()
|
|
248
|
-
if
|
|
253
|
+
if (
|
|
254
|
+
not self.access_key_id
|
|
255
|
+
or not self.access_key_secret
|
|
256
|
+
or not self.bucket_name
|
|
257
|
+
):
|
|
249
258
|
raise RuntimeError(
|
|
250
259
|
"Missing AccessKey for OSS. Set either OSS_ACCESS_KEY_ID/OSS_ACCESS_KEY_SECRET "
|
|
251
|
-
"or ALIBABA_CLOUD_ACCESS_KEY_ID/ALIBABA_CLOUD_ACCESS_KEY_SECRET.",
|
|
260
|
+
"or ALIBABA_CLOUD_ACCESS_KEY_ID/ALIBABA_CLOUD_ACCESS_KEY_SECRET or OSS_BUCKET_NAME.",
|
|
252
261
|
)
|
|
253
262
|
|
|
254
263
|
|
|
@@ -278,6 +287,7 @@ class AgentRunDeployManager(DeployManager):
|
|
|
278
287
|
oss_config: Optional[OSSConfig] = None,
|
|
279
288
|
agentrun_config: Optional[AgentRunConfig] = None,
|
|
280
289
|
build_root: Optional[Union[str, Path]] = None,
|
|
290
|
+
state_manager=None,
|
|
281
291
|
):
|
|
282
292
|
"""Initialize AgentRun deployment manager.
|
|
283
293
|
|
|
@@ -285,8 +295,9 @@ class AgentRunDeployManager(DeployManager):
|
|
|
285
295
|
oss_config: OSS configuration for artifact storage. If None, loads from environment.
|
|
286
296
|
agentrun_config: AgentRun service configuration. If None, loads from environment.
|
|
287
297
|
build_root: Root directory for build artifacts. If None, uses parent directory of current working directory.
|
|
298
|
+
state_manager: Deployment state manager. If None, creates a new instance.
|
|
288
299
|
"""
|
|
289
|
-
super().__init__()
|
|
300
|
+
super().__init__(state_manager=state_manager)
|
|
290
301
|
self.oss_config = oss_config or OSSConfig.from_env()
|
|
291
302
|
self.agentrun_config = agentrun_config or AgentRunConfig.from_env()
|
|
292
303
|
self.build_root = (
|
|
@@ -416,18 +427,19 @@ class AgentRunDeployManager(DeployManager):
|
|
|
416
427
|
)
|
|
417
428
|
|
|
418
429
|
name = deploy_name or default_deploy_name()
|
|
419
|
-
|
|
430
|
+
|
|
431
|
+
# Generate build directory with platform-aware naming
|
|
432
|
+
# proj_root = project_dir.resolve()
|
|
420
433
|
if isinstance(self.build_root, Path):
|
|
421
434
|
effective_build_root = self.build_root.resolve()
|
|
422
435
|
else:
|
|
423
436
|
if self.build_root:
|
|
424
437
|
effective_build_root = Path(self.build_root).resolve()
|
|
425
438
|
else:
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
).resolve()
|
|
439
|
+
# Use centralized directory generation function
|
|
440
|
+
effective_build_root = generate_build_directory("agentrun")
|
|
429
441
|
|
|
430
|
-
build_dir = effective_build_root
|
|
442
|
+
build_dir = effective_build_root
|
|
431
443
|
build_dir.mkdir(parents=True, exist_ok=True)
|
|
432
444
|
|
|
433
445
|
logger.info("Generating wrapper project: %s", name)
|
|
@@ -442,6 +454,7 @@ class AgentRunDeployManager(DeployManager):
|
|
|
442
454
|
logger.info("Building wheel package from: %s", wrapper_project_dir)
|
|
443
455
|
wheel_path = build_wheel(wrapper_project_dir)
|
|
444
456
|
logger.info("Wheel package created: %s", wheel_path)
|
|
457
|
+
|
|
445
458
|
return wheel_path, name
|
|
446
459
|
|
|
447
460
|
def _generate_env_file(
|
|
@@ -560,9 +573,14 @@ class AgentRunDeployManager(DeployManager):
|
|
|
560
573
|
FileNotFoundError: If specified files/directories don't exist.
|
|
561
574
|
"""
|
|
562
575
|
if not agentrun_id:
|
|
563
|
-
if
|
|
576
|
+
if (
|
|
577
|
+
not app
|
|
578
|
+
and not runner
|
|
579
|
+
and not project_dir
|
|
580
|
+
and not external_whl_path
|
|
581
|
+
):
|
|
564
582
|
raise ValueError(
|
|
565
|
-
"Must provide either runner, project_dir, or external_whl_path",
|
|
583
|
+
"Must provide either app, runner, project_dir, or external_whl_path",
|
|
566
584
|
)
|
|
567
585
|
try:
|
|
568
586
|
if runner or app:
|
|
@@ -579,6 +597,7 @@ class AgentRunDeployManager(DeployManager):
|
|
|
579
597
|
protocol_adapters=protocol_adapters,
|
|
580
598
|
requirements=requirements,
|
|
581
599
|
extra_packages=extra_packages,
|
|
600
|
+
platform="agentrun",
|
|
582
601
|
**kwargs,
|
|
583
602
|
)
|
|
584
603
|
if project_dir:
|
|
@@ -647,7 +666,7 @@ class AgentRunDeployManager(DeployManager):
|
|
|
647
666
|
logger.info("Uploading zip package to OSS")
|
|
648
667
|
oss_result = await self._upload_to_fixed_oss_bucket(
|
|
649
668
|
zip_file_path=zip_file_path,
|
|
650
|
-
bucket_name=
|
|
669
|
+
bucket_name=self.oss_config.bucket_name,
|
|
651
670
|
)
|
|
652
671
|
logger.info("Zip package uploaded to OSS successfully")
|
|
653
672
|
|
|
@@ -661,21 +680,50 @@ class AgentRunDeployManager(DeployManager):
|
|
|
661
680
|
environment=environment,
|
|
662
681
|
)
|
|
663
682
|
|
|
683
|
+
# Use base class UUID deploy_id (already set in __init__)
|
|
684
|
+
deploy_id = self.deploy_id
|
|
685
|
+
agent_runtime_id = agentrun_deploy_result["agent_runtime_id"]
|
|
686
|
+
endpoint_url = agentrun_deploy_result.get(
|
|
687
|
+
"agent_runtime_public_endpoint_url",
|
|
688
|
+
"",
|
|
689
|
+
)
|
|
690
|
+
console_url = (
|
|
691
|
+
f"https://functionai.console.aliyun.com/{self.agentrun_config.region_id}/"
|
|
692
|
+
f"agent/infra/agent-runtime/agent-detail?id={agent_runtime_id}"
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# Save deployment to state manager
|
|
696
|
+
deployment = Deployment(
|
|
697
|
+
id=deploy_id,
|
|
698
|
+
platform="agentrun",
|
|
699
|
+
url=console_url,
|
|
700
|
+
status="running",
|
|
701
|
+
created_at=datetime.now().isoformat(),
|
|
702
|
+
agent_source=kwargs.get("agent_source"),
|
|
703
|
+
config={
|
|
704
|
+
"agent_runtime_id": agent_runtime_id,
|
|
705
|
+
"agent_runtime_endpoint_url": endpoint_url,
|
|
706
|
+
"resource_name": name,
|
|
707
|
+
"wheel_path": str(wheel_path),
|
|
708
|
+
"artifact_url": oss_result.get("presigned_url", ""),
|
|
709
|
+
"region_id": self.agentrun_config.region_id,
|
|
710
|
+
},
|
|
711
|
+
)
|
|
712
|
+
self.state_manager.save(deployment)
|
|
713
|
+
|
|
664
714
|
# Return deployment results
|
|
665
715
|
logger.info(
|
|
666
716
|
"Deployment completed successfully. Agent runtime ID: %s",
|
|
667
|
-
|
|
717
|
+
agent_runtime_id,
|
|
668
718
|
)
|
|
669
719
|
return {
|
|
670
720
|
"message": "Agent deployed successfully to AgentRun",
|
|
671
|
-
"agentrun_id":
|
|
672
|
-
"agentrun_endpoint_url":
|
|
673
|
-
"agent_runtime_public_endpoint_url"
|
|
674
|
-
],
|
|
721
|
+
"agentrun_id": agent_runtime_id,
|
|
722
|
+
"agentrun_endpoint_url": endpoint_url,
|
|
675
723
|
"wheel_path": str(wheel_path),
|
|
676
|
-
"artifact_url": oss_result
|
|
677
|
-
"url":
|
|
678
|
-
"deploy_id":
|
|
724
|
+
"artifact_url": oss_result.get("presigned_url", ""),
|
|
725
|
+
"url": console_url,
|
|
726
|
+
"deploy_id": deploy_id,
|
|
679
727
|
"resource_name": name,
|
|
680
728
|
}
|
|
681
729
|
|
|
@@ -861,7 +909,6 @@ ls -lh /output/{zip_filename}
|
|
|
861
909
|
from alibabacloud_oss_v2.credentials import (
|
|
862
910
|
StaticCredentialsProvider,
|
|
863
911
|
)
|
|
864
|
-
import datetime
|
|
865
912
|
except ImportError as e:
|
|
866
913
|
logger.error(
|
|
867
914
|
"OSS SDK not available. Install with: pip install alibabacloud-oss-v2",
|
|
@@ -2539,3 +2586,86 @@ ls -lh /output/{zip_filename}
|
|
|
2539
2586
|
"error": str(e),
|
|
2540
2587
|
"message": f"Exception occurred while publishing agent runtime version: {str(e)}",
|
|
2541
2588
|
}
|
|
2589
|
+
|
|
2590
|
+
async def stop(self, deploy_id: str, **kwargs) -> Dict[str, Any]:
|
|
2591
|
+
"""Stop AgentRun deployment by deleting it.
|
|
2592
|
+
|
|
2593
|
+
Args:
|
|
2594
|
+
deploy_id: AgentRun runtime ID (agent_runtime_id)
|
|
2595
|
+
**kwargs: Additional parameters
|
|
2596
|
+
|
|
2597
|
+
Returns:
|
|
2598
|
+
Dict with success status, message, and details
|
|
2599
|
+
"""
|
|
2600
|
+
try:
|
|
2601
|
+
# Try to get deployment info from state for context
|
|
2602
|
+
deployment_info = None
|
|
2603
|
+
deployment = None
|
|
2604
|
+
try:
|
|
2605
|
+
deployment = self.state_manager.get(deploy_id)
|
|
2606
|
+
if deployment:
|
|
2607
|
+
deployment_info = {
|
|
2608
|
+
"url": deployment.url
|
|
2609
|
+
if hasattr(deployment, "url")
|
|
2610
|
+
else None,
|
|
2611
|
+
"resource_name": getattr(
|
|
2612
|
+
deployment,
|
|
2613
|
+
"resource_name",
|
|
2614
|
+
None,
|
|
2615
|
+
),
|
|
2616
|
+
}
|
|
2617
|
+
logger.debug(
|
|
2618
|
+
f"Fetched deployment info from state: {deployment_info}",
|
|
2619
|
+
)
|
|
2620
|
+
except Exception as e:
|
|
2621
|
+
logger.debug(
|
|
2622
|
+
f"Could not fetch deployment info from state: {e}",
|
|
2623
|
+
)
|
|
2624
|
+
|
|
2625
|
+
logger.info(f"Stopping AgentRun deployment: {deploy_id}")
|
|
2626
|
+
|
|
2627
|
+
# Get agent_runtime_id from deployment config
|
|
2628
|
+
agent_runtime_id = None
|
|
2629
|
+
if deployment and deployment.config:
|
|
2630
|
+
agent_runtime_id = deployment.config.get("agent_runtime_id")
|
|
2631
|
+
|
|
2632
|
+
if not agent_runtime_id:
|
|
2633
|
+
# Fallback: try using deploy_id as agent_runtime_id for backward compatibility
|
|
2634
|
+
agent_runtime_id = deploy_id
|
|
2635
|
+
logger.warning(
|
|
2636
|
+
f"Could not find agent_runtime_id in deployment config, "
|
|
2637
|
+
f"using deploy_id as fallback: {deploy_id}",
|
|
2638
|
+
)
|
|
2639
|
+
|
|
2640
|
+
# Use the existing delete method with agent_runtime_id
|
|
2641
|
+
result = await self.delete(agent_runtime_id)
|
|
2642
|
+
|
|
2643
|
+
if result.get("success"):
|
|
2644
|
+
# Remove from state manager on successful deletion
|
|
2645
|
+
try:
|
|
2646
|
+
self.state_manager.update_status(deploy_id, "stopped")
|
|
2647
|
+
except KeyError:
|
|
2648
|
+
logger.debug(
|
|
2649
|
+
f"Deployment {deploy_id} not found in state (already removed)",
|
|
2650
|
+
)
|
|
2651
|
+
|
|
2652
|
+
return {
|
|
2653
|
+
"success": True,
|
|
2654
|
+
"message": f"AgentRun deployment {deploy_id} deleted successfully",
|
|
2655
|
+
"details": result,
|
|
2656
|
+
}
|
|
2657
|
+
else:
|
|
2658
|
+
return {
|
|
2659
|
+
"success": False,
|
|
2660
|
+
"message": f"Failed to delete AgentRun deployment: {result.get('message', 'Unknown error')}",
|
|
2661
|
+
"details": result,
|
|
2662
|
+
}
|
|
2663
|
+
except Exception as e:
|
|
2664
|
+
logger.error(
|
|
2665
|
+
f"Failed to stop AgentRun deployment {deploy_id}: {e}",
|
|
2666
|
+
)
|
|
2667
|
+
return {
|
|
2668
|
+
"success": False,
|
|
2669
|
+
"message": f"Failed to stop AgentRun deployment: {e}",
|
|
2670
|
+
"details": {"deploy_id": deploy_id, "error": str(e)},
|
|
2671
|
+
}
|
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import uuid
|
|
3
3
|
from abc import abstractmethod, ABC
|
|
4
|
-
from typing import Dict
|
|
4
|
+
from typing import Dict, Any
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# there is not many attributes in it, consider it as interface, instead of
|
|
8
8
|
# pydantic BaseModel
|
|
9
9
|
class DeployManager(ABC):
|
|
10
|
-
def __init__(self):
|
|
10
|
+
def __init__(self, state_manager=None):
|
|
11
11
|
self.deploy_id = str(uuid.uuid4())
|
|
12
12
|
self._app = None
|
|
13
13
|
|
|
14
|
+
# Initialize state manager - shared across all deployers
|
|
15
|
+
if state_manager is None:
|
|
16
|
+
from agentscope_runtime.engine.deployers.state import (
|
|
17
|
+
DeploymentStateManager,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
state_manager = DeploymentStateManager()
|
|
21
|
+
self.state_manager = state_manager
|
|
22
|
+
|
|
14
23
|
@abstractmethod
|
|
15
24
|
async def deploy(self, *args, **kwargs) -> Dict[str, str]:
|
|
16
25
|
"""Deploy the service and return a dictionary with deploy_id and
|
|
17
26
|
URL."""
|
|
18
27
|
raise NotImplementedError
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
async def stop(self, deploy_id: str, **kwargs) -> Dict[str, Any]:
|
|
31
|
+
"""Stop the deployed service.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
deploy_id: Deployment identifier
|
|
35
|
+
**kwargs: Platform-specific parameters (url, namespace, etc.)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict with keys:
|
|
39
|
+
- success (bool): Whether stop succeeded
|
|
40
|
+
- message (str): Status message
|
|
41
|
+
- details (dict, optional): Platform-specific details
|
|
42
|
+
"""
|
|
43
|
+
raise NotImplementedError
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
import
|
|
4
|
+
from datetime import datetime
|
|
5
5
|
from typing import Optional, Dict, List, Union, Any
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
|
+
from agentscope_runtime.engine.deployers.state import Deployment
|
|
9
10
|
from .adapter.protocol_adapter import ProtocolAdapter
|
|
10
11
|
from .base import DeployManager
|
|
11
12
|
from .utils.docker_image_utils import (
|
|
12
13
|
ImageFactory,
|
|
13
14
|
RegistryConfig,
|
|
14
15
|
)
|
|
16
|
+
from .utils.k8s_utils import isLocalK8sEnvironment
|
|
15
17
|
from ...common.container_clients.kubernetes_client import (
|
|
16
18
|
KubernetesClient,
|
|
17
19
|
)
|
|
@@ -36,7 +38,7 @@ class K8sConfig(BaseModel):
|
|
|
36
38
|
class BuildConfig(BaseModel):
|
|
37
39
|
"""Build configuration"""
|
|
38
40
|
|
|
39
|
-
build_context_dir: str =
|
|
41
|
+
build_context_dir: Optional[str] = None # None allows caching
|
|
40
42
|
dockerfile_template: str = None
|
|
41
43
|
build_timeout: int = 600 # 10 minutes
|
|
42
44
|
push_timeout: int = 300 # 5 minutes
|
|
@@ -51,15 +53,15 @@ class KubernetesDeployManager(DeployManager):
|
|
|
51
53
|
kube_config: K8sConfig = None,
|
|
52
54
|
registry_config: RegistryConfig = RegistryConfig(),
|
|
53
55
|
use_deployment: bool = True,
|
|
54
|
-
build_context_dir: str =
|
|
56
|
+
build_context_dir: Optional[str] = None,
|
|
57
|
+
state_manager=None,
|
|
55
58
|
):
|
|
56
|
-
super().__init__()
|
|
59
|
+
super().__init__(state_manager=state_manager)
|
|
57
60
|
self.kubeconfig = kube_config
|
|
58
61
|
self.registry_config = registry_config
|
|
59
62
|
self.image_factory = ImageFactory()
|
|
60
63
|
self.use_deployment = use_deployment
|
|
61
64
|
self.build_context_dir = build_context_dir
|
|
62
|
-
self._deployed_resources = {}
|
|
63
65
|
self._built_images = {}
|
|
64
66
|
|
|
65
67
|
self.k8s_client = KubernetesClient(
|
|
@@ -67,10 +69,65 @@ class KubernetesDeployManager(DeployManager):
|
|
|
67
69
|
image_registry=self.registry_config.get_full_url(),
|
|
68
70
|
)
|
|
69
71
|
|
|
72
|
+
@staticmethod
|
|
73
|
+
def get_service_endpoint(
|
|
74
|
+
service_external_ip: Optional[str],
|
|
75
|
+
service_port: Optional[Union[int, list]],
|
|
76
|
+
fallback_host: str = "127.0.0.1",
|
|
77
|
+
) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Auto-select appropriate service endpoint based on detected environment.
|
|
80
|
+
|
|
81
|
+
Solves the common issue where Kubernetes LoadBalancer/ExternalIP is not
|
|
82
|
+
reachable from localhost in local clusters (e.g., Minikube/Kind).
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
service_external_ip: ExternalIP or LoadBalancer IP from Service
|
|
86
|
+
service_port: Target port
|
|
87
|
+
fallback_host: Host to use in local environments (default:
|
|
88
|
+
127.0.0.1)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
str: Full HTTP endpoint URL: http://<host>:<port>
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> endpoint = get_service_endpoint('192.168.5.1', 8080)
|
|
95
|
+
>>> # In local env → 'http://127.0.0.1:8080'
|
|
96
|
+
>>> # In cloud env → 'http://192.168.5.1:8080'
|
|
97
|
+
"""
|
|
98
|
+
if not service_external_ip:
|
|
99
|
+
service_external_ip = "127.0.0.1"
|
|
100
|
+
|
|
101
|
+
if not service_port:
|
|
102
|
+
service_port = 8080
|
|
103
|
+
|
|
104
|
+
if isinstance(service_port, list):
|
|
105
|
+
service_port = service_port[0]
|
|
106
|
+
|
|
107
|
+
if isLocalK8sEnvironment():
|
|
108
|
+
host = fallback_host
|
|
109
|
+
logger.info(
|
|
110
|
+
f"Local K8s environment detected; using {host} instead of "
|
|
111
|
+
f"{service_external_ip}",
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
host = service_external_ip
|
|
115
|
+
logger.info(
|
|
116
|
+
f"Cloud/remote environment detected; using External IP: "
|
|
117
|
+
f"{host}",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return f"http://{host}:{service_port}"
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def get_resource_name(deploy_id: str) -> str:
|
|
124
|
+
return f"agent-{deploy_id[:8]}"
|
|
125
|
+
|
|
70
126
|
async def deploy(
|
|
71
127
|
self,
|
|
72
128
|
app=None,
|
|
73
129
|
runner=None,
|
|
130
|
+
entrypoint: Optional[str] = None,
|
|
74
131
|
endpoint_path: str = "/process",
|
|
75
132
|
stream: bool = True,
|
|
76
133
|
custom_endpoints: Optional[List[Dict]] = None,
|
|
@@ -86,15 +143,20 @@ class KubernetesDeployManager(DeployManager):
|
|
|
86
143
|
image_name: str = "agent_llm",
|
|
87
144
|
image_tag: str = "latest",
|
|
88
145
|
push_to_registry: bool = False,
|
|
146
|
+
use_cache: bool = True,
|
|
147
|
+
pypi_mirror: Optional[str] = None,
|
|
89
148
|
**kwargs,
|
|
90
149
|
) -> Dict[str, Any]:
|
|
91
150
|
"""
|
|
92
151
|
Deploy runner to Kubernetes.
|
|
93
152
|
|
|
153
|
+
All temporary files are created in cwd/.agentscope_runtime/ by default.
|
|
154
|
+
|
|
94
155
|
Args:
|
|
95
156
|
app: Agent app to be deployed
|
|
96
157
|
runner: Complete Runner object with agent, environment_manager,
|
|
97
158
|
context_manager
|
|
159
|
+
entrypoint: Entrypoint spec (e.g., "app.py" or "app.py:handler")
|
|
98
160
|
endpoint_path: API endpoint path
|
|
99
161
|
stream: Enable streaming responses
|
|
100
162
|
custom_endpoints: Custom endpoints from agent app
|
|
@@ -108,6 +170,8 @@ class KubernetesDeployManager(DeployManager):
|
|
|
108
170
|
environment: Environment variables dict
|
|
109
171
|
mount_dir: Mount directory
|
|
110
172
|
runtime_config: K8s runtime configuration
|
|
173
|
+
use_cache: Enable build cache (default: True)
|
|
174
|
+
pypi_mirror: PyPI mirror URL for pip package installation
|
|
111
175
|
# Backward compatibility
|
|
112
176
|
image_name: Image name
|
|
113
177
|
image_tag: Image tag
|
|
@@ -132,6 +196,7 @@ class KubernetesDeployManager(DeployManager):
|
|
|
132
196
|
built_image_name = self.image_factory.build_image(
|
|
133
197
|
app=app,
|
|
134
198
|
runner=runner,
|
|
199
|
+
entrypoint=entrypoint,
|
|
135
200
|
requirements=requirements,
|
|
136
201
|
extra_packages=extra_packages or [],
|
|
137
202
|
base_image=base_image,
|
|
@@ -145,6 +210,8 @@ class KubernetesDeployManager(DeployManager):
|
|
|
145
210
|
port=port,
|
|
146
211
|
protocol_adapters=protocol_adapters,
|
|
147
212
|
custom_endpoints=custom_endpoints,
|
|
213
|
+
use_cache=use_cache,
|
|
214
|
+
pypi_mirror=pypi_mirror,
|
|
148
215
|
**kwargs,
|
|
149
216
|
)
|
|
150
217
|
if not built_image_name:
|
|
@@ -173,7 +240,7 @@ class KubernetesDeployManager(DeployManager):
|
|
|
173
240
|
else:
|
|
174
241
|
volume_bindings = {}
|
|
175
242
|
|
|
176
|
-
resource_name =
|
|
243
|
+
resource_name = self.get_resource_name(deploy_id)
|
|
177
244
|
|
|
178
245
|
logger.info(f"Building kubernetes deployment for {deploy_id}")
|
|
179
246
|
|
|
@@ -197,33 +264,36 @@ class KubernetesDeployManager(DeployManager):
|
|
|
197
264
|
)
|
|
198
265
|
|
|
199
266
|
if ports:
|
|
200
|
-
url =
|
|
267
|
+
url = self.get_service_endpoint(ip, ports)
|
|
201
268
|
else:
|
|
202
|
-
url =
|
|
269
|
+
url = self.get_service_endpoint(ip, port)
|
|
203
270
|
|
|
204
271
|
logger.info(f"Deployment {deploy_id} successful: {url}")
|
|
205
272
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
"
|
|
213
|
-
|
|
273
|
+
deployment = Deployment(
|
|
274
|
+
id=deploy_id,
|
|
275
|
+
platform="k8s",
|
|
276
|
+
url=url,
|
|
277
|
+
status="running",
|
|
278
|
+
created_at=datetime.now().isoformat(),
|
|
279
|
+
agent_source=kwargs.get("agent_source"),
|
|
280
|
+
config={
|
|
281
|
+
"service_name": _id,
|
|
282
|
+
"image": built_image_name,
|
|
283
|
+
"replicas": replicas if self.use_deployment else 1,
|
|
284
|
+
"runner": runner.__class__.__name__ if runner else None,
|
|
214
285
|
"extra_packages": extra_packages,
|
|
215
|
-
"requirements": requirements,
|
|
286
|
+
"requirements": requirements,
|
|
216
287
|
"base_image": base_image,
|
|
217
288
|
"port": port,
|
|
218
|
-
"replicas": replicas,
|
|
219
289
|
"environment": environment,
|
|
220
290
|
"runtime_config": runtime_config,
|
|
221
291
|
"endpoint_path": endpoint_path,
|
|
222
292
|
"stream": stream,
|
|
223
|
-
"protocol_adapters": protocol_adapters,
|
|
224
|
-
**kwargs,
|
|
225
293
|
},
|
|
226
|
-
|
|
294
|
+
)
|
|
295
|
+
self.state_manager.save(deployment)
|
|
296
|
+
|
|
227
297
|
return {
|
|
228
298
|
"deploy_id": deploy_id,
|
|
229
299
|
"url": url,
|
|
@@ -240,21 +310,81 @@ class KubernetesDeployManager(DeployManager):
|
|
|
240
310
|
f"Deployment failed: {e}, {traceback.format_exc()}",
|
|
241
311
|
) from e
|
|
242
312
|
|
|
243
|
-
async def stop(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
313
|
+
async def stop(
|
|
314
|
+
self,
|
|
315
|
+
deploy_id: str,
|
|
316
|
+
**kwargs,
|
|
317
|
+
) -> Dict[str, Any]:
|
|
318
|
+
"""Stop Kubernetes deployment.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
deploy_id: Deployment identifier
|
|
322
|
+
**kwargs: Additional parameters
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Dict with success status, message, and details
|
|
326
|
+
"""
|
|
327
|
+
# Derive resource name from deploy_id
|
|
328
|
+
resource_name = self.get_resource_name(deploy_id)
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
# Try to remove the deployment
|
|
332
|
+
success = self.k8s_client.remove_deployment(resource_name)
|
|
333
|
+
|
|
334
|
+
if success:
|
|
335
|
+
# Remove from state manager
|
|
336
|
+
try:
|
|
337
|
+
self.state_manager.update_status(deploy_id, "stopped")
|
|
338
|
+
except KeyError:
|
|
339
|
+
logger.debug(
|
|
340
|
+
f"Deployment {deploy_id} not found "
|
|
341
|
+
f"in state (already removed)",
|
|
342
|
+
)
|
|
247
343
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
344
|
+
return {
|
|
345
|
+
"success": True,
|
|
346
|
+
"message": f"Kubernetes deployment {resource_name} "
|
|
347
|
+
f"removed",
|
|
348
|
+
"details": {
|
|
349
|
+
"deploy_id": deploy_id,
|
|
350
|
+
"resource_name": resource_name,
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
else:
|
|
354
|
+
return {
|
|
355
|
+
"success": False,
|
|
356
|
+
"message": f"Kubernetes deployment {resource_name} not "
|
|
357
|
+
f"found (may already be deleted), Please check the "
|
|
358
|
+
f"detail in cluster",
|
|
359
|
+
"details": {
|
|
360
|
+
"deploy_id": deploy_id,
|
|
361
|
+
"resource_name": resource_name,
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
except Exception as e:
|
|
365
|
+
logger.error(
|
|
366
|
+
f"Failed to remove K8s deployment {resource_name}: {e}",
|
|
367
|
+
)
|
|
368
|
+
return {
|
|
369
|
+
"success": False,
|
|
370
|
+
"message": f"Failed to remove K8s deployment: {e}",
|
|
371
|
+
"details": {
|
|
372
|
+
"deploy_id": deploy_id,
|
|
373
|
+
"resource_name": resource_name,
|
|
374
|
+
"error": str(e),
|
|
375
|
+
},
|
|
376
|
+
}
|
|
251
377
|
|
|
252
378
|
def get_status(self) -> str:
|
|
253
379
|
"""Get deployment status"""
|
|
254
|
-
|
|
380
|
+
deployment = self.state_manager.get(self.deploy_id)
|
|
381
|
+
if not deployment:
|
|
255
382
|
return "not_found"
|
|
256
383
|
|
|
257
|
-
|
|
258
|
-
|
|
384
|
+
# Get service_name from config
|
|
385
|
+
config = getattr(deployment, "config", {})
|
|
386
|
+
service_name = config.get("service_name")
|
|
387
|
+
if not service_name:
|
|
388
|
+
return "unknown"
|
|
259
389
|
|
|
260
390
|
return self.k8s_client.get_deployment_status(service_name)
|