agentscope-runtime 0.1.4__py3-none-any.whl → 0.1.5b1__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/engine/agents/agentscope_agent/agent.py +3 -0
- agentscope_runtime/engine/deployers/__init__.py +13 -0
- agentscope_runtime/engine/deployers/adapter/responses/__init__.py +0 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +2886 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_agent_adapter.py +51 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +314 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +143 -0
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +265 -0
- agentscope_runtime/engine/deployers/local_deployer.py +356 -501
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +626 -0
- agentscope_runtime/engine/deployers/utils/__init__.py +0 -0
- agentscope_runtime/engine/deployers/utils/deployment_modes.py +14 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +8 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/docker_image_builder.py +429 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +240 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +297 -0
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +932 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +9 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +504 -0
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +157 -0
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +268 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +75 -0
- agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +220 -0
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +389 -0
- agentscope_runtime/engine/helpers/agent_api_builder.py +651 -0
- agentscope_runtime/engine/runner.py +36 -10
- agentscope_runtime/engine/schemas/agent_schemas.py +70 -2
- agentscope_runtime/engine/schemas/embedding.py +37 -0
- agentscope_runtime/engine/schemas/modelstudio_llm.py +310 -0
- agentscope_runtime/engine/schemas/oai_llm.py +538 -0
- agentscope_runtime/engine/schemas/realtime.py +254 -0
- agentscope_runtime/engine/services/mem0_memory_service.py +124 -0
- agentscope_runtime/engine/services/memory_service.py +2 -1
- agentscope_runtime/engine/services/redis_session_history_service.py +4 -3
- agentscope_runtime/engine/services/session_history_service.py +4 -3
- agentscope_runtime/sandbox/manager/container_clients/kubernetes_client.py +555 -10
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.1.4.dist-info → agentscope_runtime-0.1.5b1.dist-info}/METADATA +21 -4
- {agentscope_runtime-0.1.4.dist-info → agentscope_runtime-0.1.5b1.dist-info}/RECORD +43 -16
- {agentscope_runtime-0.1.4.dist-info → agentscope_runtime-0.1.5b1.dist-info}/entry_points.txt +1 -0
- {agentscope_runtime-0.1.4.dist-info → agentscope_runtime-0.1.5b1.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.1.4.dist-info → agentscope_runtime-0.1.5b1.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.1.4.dist-info → agentscope_runtime-0.1.5b1.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
# pylint: disable=too-many-branches
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
# pylint: disable=too-many-branches, self-assigning-variable
|
|
3
|
+
# pylint: disable=too-many-statements
|
|
4
|
+
|
|
5
5
|
import hashlib
|
|
6
|
-
import traceback
|
|
7
6
|
import logging
|
|
7
|
+
import time
|
|
8
|
+
import traceback
|
|
9
|
+
from typing import Optional, Tuple
|
|
8
10
|
|
|
9
11
|
from kubernetes import client
|
|
10
12
|
from kubernetes import config as k8s_config
|
|
@@ -14,12 +16,19 @@ from .base_client import BaseClient
|
|
|
14
16
|
|
|
15
17
|
logger = logging.getLogger(__name__)
|
|
16
18
|
|
|
19
|
+
DEFAULT_IMAGE_REGISTRY = "agentscope-registry.ap-southeast-1.cr.aliyuncs.com"
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
class KubernetesClient(BaseClient):
|
|
19
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
config=None,
|
|
26
|
+
image_registry: Optional[str] = DEFAULT_IMAGE_REGISTRY,
|
|
27
|
+
):
|
|
20
28
|
self.config = config
|
|
21
29
|
namespace = self.config.k8s_namespace
|
|
22
30
|
kubeconfig = self.config.kubeconfig_path
|
|
31
|
+
self.image_registry = image_registry
|
|
23
32
|
try:
|
|
24
33
|
if kubeconfig:
|
|
25
34
|
k8s_config.load_kube_config(config_file=kubeconfig)
|
|
@@ -31,6 +40,7 @@ class KubernetesClient(BaseClient):
|
|
|
31
40
|
except k8s_config.ConfigException:
|
|
32
41
|
k8s_config.load_kube_config()
|
|
33
42
|
self.v1 = client.CoreV1Api()
|
|
43
|
+
self.apps_v1 = client.AppsV1Api() # For Deployments
|
|
34
44
|
self.namespace = namespace
|
|
35
45
|
# Test connection
|
|
36
46
|
self.v1.list_namespace()
|
|
@@ -45,6 +55,61 @@ class KubernetesClient(BaseClient):
|
|
|
45
55
|
"• For in-cluster: ensure proper RBAC permissions",
|
|
46
56
|
) from e
|
|
47
57
|
|
|
58
|
+
def _is_local_cluster(self):
|
|
59
|
+
"""
|
|
60
|
+
Determine if we're connected to a local Kubernetes cluster.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
bool: True if connected to a local cluster, False otherwise
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
# Get the current context configuration
|
|
67
|
+
contexts, current_context = k8s_config.list_kube_config_contexts(
|
|
68
|
+
config_file=self.config.kubeconfig_path
|
|
69
|
+
if hasattr(self.config, "kubeconfig_path")
|
|
70
|
+
and self.config.kubeconfig_path
|
|
71
|
+
else None,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if current_context and current_context.get("context"):
|
|
75
|
+
cluster_name = current_context["context"].get("cluster", "")
|
|
76
|
+
server = None
|
|
77
|
+
|
|
78
|
+
# Get cluster server URL
|
|
79
|
+
for cluster in contexts.get("clusters", []):
|
|
80
|
+
if cluster["name"] == cluster_name:
|
|
81
|
+
server = cluster.get("cluster", {}).get("server", "")
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
if server:
|
|
85
|
+
# Check for common local cluster patterns
|
|
86
|
+
local_patterns = [
|
|
87
|
+
"localhost",
|
|
88
|
+
"127.0.0.1",
|
|
89
|
+
"0.0.0.0",
|
|
90
|
+
"docker-desktop",
|
|
91
|
+
"kind-", # kind clusters
|
|
92
|
+
"minikube", # minikube
|
|
93
|
+
"k3d-", # k3d clusters
|
|
94
|
+
"colima", # colima
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
server_lower = server.lower()
|
|
98
|
+
cluster_lower = cluster_name.lower()
|
|
99
|
+
|
|
100
|
+
for pattern in local_patterns:
|
|
101
|
+
if pattern in server_lower or pattern in cluster_lower:
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(
|
|
108
|
+
f"Could not determine cluster type, assuming remote: {e}",
|
|
109
|
+
)
|
|
110
|
+
# If we can't determine, assume remote for safety
|
|
111
|
+
return False
|
|
112
|
+
|
|
48
113
|
def _parse_port_spec(self, port_spec):
|
|
49
114
|
"""
|
|
50
115
|
Parse port specification.
|
|
@@ -93,10 +158,15 @@ class KubernetesClient(BaseClient):
|
|
|
93
158
|
|
|
94
159
|
# Container specification
|
|
95
160
|
# TODO: use image from docker registry first
|
|
161
|
+
|
|
162
|
+
if not self.image_registry:
|
|
163
|
+
image = image
|
|
164
|
+
else:
|
|
165
|
+
image = f"{self.image_registry}/{image}"
|
|
166
|
+
|
|
96
167
|
container = client.V1Container(
|
|
97
168
|
name=container_name,
|
|
98
|
-
image=
|
|
99
|
-
f"/{image}",
|
|
169
|
+
image=image,
|
|
100
170
|
image_pull_policy=runtime_config.get(
|
|
101
171
|
"image_pull_policy",
|
|
102
172
|
"IfNotPresent",
|
|
@@ -564,9 +634,8 @@ class KubernetesClient(BaseClient):
|
|
|
564
634
|
def _get_pod_node_ip(self, pod_name):
|
|
565
635
|
"""Get the IP of the node where the pod is running"""
|
|
566
636
|
|
|
567
|
-
# Check if we are
|
|
568
|
-
|
|
569
|
-
if "colima" in docker_host.lower():
|
|
637
|
+
# Check if we are using a local Kubernetes cluster
|
|
638
|
+
if self._is_local_cluster():
|
|
570
639
|
return "localhost"
|
|
571
640
|
|
|
572
641
|
try:
|
|
@@ -603,3 +672,479 @@ class KubernetesClient(BaseClient):
|
|
|
603
672
|
except Exception as e:
|
|
604
673
|
logger.error(f"Failed to get pod node IP: {e}")
|
|
605
674
|
return None
|
|
675
|
+
|
|
676
|
+
def _create_deployment_spec(
|
|
677
|
+
self,
|
|
678
|
+
image,
|
|
679
|
+
name,
|
|
680
|
+
ports=None,
|
|
681
|
+
volumes=None,
|
|
682
|
+
environment=None,
|
|
683
|
+
runtime_config=None,
|
|
684
|
+
replicas=1,
|
|
685
|
+
):
|
|
686
|
+
"""Create a Kubernetes Deployment specification."""
|
|
687
|
+
if runtime_config is None:
|
|
688
|
+
runtime_config = {}
|
|
689
|
+
|
|
690
|
+
container_name = name or "main-container"
|
|
691
|
+
|
|
692
|
+
# Use image registry if configured
|
|
693
|
+
if not self.image_registry:
|
|
694
|
+
full_image = image
|
|
695
|
+
else:
|
|
696
|
+
full_image = f"{self.image_registry}/{image}"
|
|
697
|
+
|
|
698
|
+
# Create container spec (reuse the existing pod spec logic)
|
|
699
|
+
container = client.V1Container(
|
|
700
|
+
name=container_name,
|
|
701
|
+
image=full_image,
|
|
702
|
+
image_pull_policy=runtime_config.get(
|
|
703
|
+
"image_pull_policy",
|
|
704
|
+
"IfNotPresent",
|
|
705
|
+
),
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
# Configure ports
|
|
709
|
+
if ports:
|
|
710
|
+
container_ports = []
|
|
711
|
+
for port_spec in ports:
|
|
712
|
+
port_info = self._parse_port_spec(port_spec)
|
|
713
|
+
if port_info:
|
|
714
|
+
container_ports.append(
|
|
715
|
+
client.V1ContainerPort(
|
|
716
|
+
container_port=port_info["port"],
|
|
717
|
+
protocol=port_info["protocol"],
|
|
718
|
+
),
|
|
719
|
+
)
|
|
720
|
+
if container_ports:
|
|
721
|
+
container.ports = container_ports
|
|
722
|
+
|
|
723
|
+
# Configure environment variables
|
|
724
|
+
if environment:
|
|
725
|
+
env_vars = []
|
|
726
|
+
for key, value in environment.items():
|
|
727
|
+
env_vars.append(client.V1EnvVar(name=key, value=str(value)))
|
|
728
|
+
container.env = env_vars
|
|
729
|
+
|
|
730
|
+
# Configure volume mounts and volumes
|
|
731
|
+
volume_mounts = []
|
|
732
|
+
pod_volumes = []
|
|
733
|
+
if volumes:
|
|
734
|
+
for volume_idx, (host_path, mount_info) in enumerate(
|
|
735
|
+
volumes.items(),
|
|
736
|
+
):
|
|
737
|
+
if isinstance(mount_info, dict):
|
|
738
|
+
container_path = mount_info["bind"]
|
|
739
|
+
mode = mount_info.get("mode", "rw")
|
|
740
|
+
else:
|
|
741
|
+
container_path = mount_info
|
|
742
|
+
mode = "rw"
|
|
743
|
+
volume_name = f"vol-{volume_idx}"
|
|
744
|
+
|
|
745
|
+
# Create volume mount
|
|
746
|
+
volume_mounts.append(
|
|
747
|
+
client.V1VolumeMount(
|
|
748
|
+
name=volume_name,
|
|
749
|
+
mount_path=container_path,
|
|
750
|
+
read_only=(mode == "ro"),
|
|
751
|
+
),
|
|
752
|
+
)
|
|
753
|
+
# Create host path volume
|
|
754
|
+
pod_volumes.append(
|
|
755
|
+
client.V1Volume(
|
|
756
|
+
name=volume_name,
|
|
757
|
+
host_path=client.V1HostPathVolumeSource(
|
|
758
|
+
path=host_path,
|
|
759
|
+
),
|
|
760
|
+
),
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
if volume_mounts:
|
|
764
|
+
container.volume_mounts = volume_mounts
|
|
765
|
+
|
|
766
|
+
# Apply runtime config to container
|
|
767
|
+
if "resources" in runtime_config:
|
|
768
|
+
container.resources = client.V1ResourceRequirements(
|
|
769
|
+
**runtime_config["resources"],
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
if "security_context" in runtime_config:
|
|
773
|
+
container.security_context = client.V1SecurityContext(
|
|
774
|
+
**runtime_config["security_context"],
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
# Pod template specification
|
|
778
|
+
pod_spec = client.V1PodSpec(
|
|
779
|
+
containers=[container],
|
|
780
|
+
restart_policy=runtime_config.get(
|
|
781
|
+
"restart_policy",
|
|
782
|
+
"Always",
|
|
783
|
+
), # Deployments typically use Always
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
if pod_volumes:
|
|
787
|
+
pod_spec.volumes = pod_volumes
|
|
788
|
+
|
|
789
|
+
if "node_selector" in runtime_config:
|
|
790
|
+
pod_spec.node_selector = runtime_config["node_selector"]
|
|
791
|
+
|
|
792
|
+
if "tolerations" in runtime_config:
|
|
793
|
+
pod_spec.tolerations = runtime_config["tolerations"]
|
|
794
|
+
|
|
795
|
+
# Handle image pull secrets
|
|
796
|
+
image_pull_secrets = runtime_config.get("image_pull_secrets", [])
|
|
797
|
+
if image_pull_secrets:
|
|
798
|
+
secrets = []
|
|
799
|
+
for secret_name in image_pull_secrets:
|
|
800
|
+
secrets.append(client.V1LocalObjectReference(name=secret_name))
|
|
801
|
+
pod_spec.image_pull_secrets = secrets
|
|
802
|
+
|
|
803
|
+
# Pod template
|
|
804
|
+
pod_template = client.V1PodTemplateSpec(
|
|
805
|
+
metadata=client.V1ObjectMeta(
|
|
806
|
+
labels={
|
|
807
|
+
"app": name,
|
|
808
|
+
"created-by": "kubernetes-client",
|
|
809
|
+
},
|
|
810
|
+
),
|
|
811
|
+
spec=pod_spec,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
# Deployment specification
|
|
815
|
+
deployment_spec = client.V1DeploymentSpec(
|
|
816
|
+
replicas=replicas,
|
|
817
|
+
selector=client.V1LabelSelector(
|
|
818
|
+
match_labels={"app": name},
|
|
819
|
+
),
|
|
820
|
+
template=pod_template,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
return deployment_spec
|
|
824
|
+
|
|
825
|
+
def _create_loadbalancer_service(self, deployment_name, port_list):
|
|
826
|
+
"""Create a LoadBalancer service for the deployment."""
|
|
827
|
+
try:
|
|
828
|
+
service_name = f"{deployment_name}-lb-service"
|
|
829
|
+
selector = {"app": deployment_name}
|
|
830
|
+
|
|
831
|
+
# Construct multi-port configuration
|
|
832
|
+
service_ports = []
|
|
833
|
+
for port_info in port_list:
|
|
834
|
+
port = port_info["port"]
|
|
835
|
+
protocol = port_info["protocol"]
|
|
836
|
+
service_ports.append(
|
|
837
|
+
client.V1ServicePort(
|
|
838
|
+
name=f"port-{port}",
|
|
839
|
+
port=port,
|
|
840
|
+
target_port=port,
|
|
841
|
+
protocol=protocol,
|
|
842
|
+
),
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
service_spec = client.V1ServiceSpec(
|
|
846
|
+
selector=selector,
|
|
847
|
+
ports=service_ports,
|
|
848
|
+
type="LoadBalancer",
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
service = client.V1Service(
|
|
852
|
+
api_version="v1",
|
|
853
|
+
kind="Service",
|
|
854
|
+
metadata=client.V1ObjectMeta(
|
|
855
|
+
name=service_name,
|
|
856
|
+
namespace=self.namespace,
|
|
857
|
+
),
|
|
858
|
+
spec=service_spec,
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
# Create the service
|
|
862
|
+
self.v1.create_namespaced_service(
|
|
863
|
+
namespace=self.namespace,
|
|
864
|
+
body=service,
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
logger.debug(
|
|
868
|
+
f"LoadBalancer service '{service_name}' created successfully",
|
|
869
|
+
)
|
|
870
|
+
return True, service_name
|
|
871
|
+
except Exception as e:
|
|
872
|
+
logger.error(
|
|
873
|
+
f"Failed to create LoadBalancer service for deployment "
|
|
874
|
+
f"{deployment_name}: "
|
|
875
|
+
f"{e}, {traceback.format_exc()}",
|
|
876
|
+
)
|
|
877
|
+
return False, None
|
|
878
|
+
|
|
879
|
+
def create_deployment(
|
|
880
|
+
self,
|
|
881
|
+
image,
|
|
882
|
+
name=None,
|
|
883
|
+
ports=None,
|
|
884
|
+
volumes=None,
|
|
885
|
+
environment=None,
|
|
886
|
+
runtime_config=None,
|
|
887
|
+
replicas=1,
|
|
888
|
+
create_service=True,
|
|
889
|
+
) -> Tuple[str, list, str] | Tuple[None, None, None]:
|
|
890
|
+
"""
|
|
891
|
+
Create a new Kubernetes Deployment with LoadBalancer service.
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
image: Container image name
|
|
895
|
+
name: Deployment name (auto-generated if None)
|
|
896
|
+
ports: List of ports to expose
|
|
897
|
+
volumes: Volume mounts dictionary
|
|
898
|
+
environment: Environment variables dictionary
|
|
899
|
+
runtime_config: Runtime configuration dictionary
|
|
900
|
+
replicas: Number of replicas for the deployment
|
|
901
|
+
create_service: Whether to create LoadBalancer service
|
|
902
|
+
|
|
903
|
+
Returns:
|
|
904
|
+
Tuple of (deployment_name, ports, load_balancer_ip)
|
|
905
|
+
"""
|
|
906
|
+
if not name:
|
|
907
|
+
name = f"deploy-{hashlib.md5(image.encode()).hexdigest()[:8]}"
|
|
908
|
+
|
|
909
|
+
try:
|
|
910
|
+
# Create deployment specification
|
|
911
|
+
deployment_spec = self._create_deployment_spec(
|
|
912
|
+
image,
|
|
913
|
+
name,
|
|
914
|
+
ports,
|
|
915
|
+
volumes,
|
|
916
|
+
environment,
|
|
917
|
+
runtime_config,
|
|
918
|
+
replicas,
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
# Create deployment metadata
|
|
922
|
+
metadata = client.V1ObjectMeta(
|
|
923
|
+
name=name,
|
|
924
|
+
namespace=self.namespace,
|
|
925
|
+
labels={
|
|
926
|
+
"created-by": "kubernetes-client",
|
|
927
|
+
"app": name,
|
|
928
|
+
},
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
# Create deployment object
|
|
932
|
+
deployment = client.V1Deployment(
|
|
933
|
+
api_version="apps/v1",
|
|
934
|
+
kind="Deployment",
|
|
935
|
+
metadata=metadata,
|
|
936
|
+
spec=deployment_spec,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
# Create the deployment
|
|
940
|
+
self.apps_v1.create_namespaced_deployment(
|
|
941
|
+
namespace=self.namespace,
|
|
942
|
+
body=deployment,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
logger.debug(
|
|
946
|
+
f"Deployment '{name}' created successfully in namespace "
|
|
947
|
+
f"'{self.namespace}' with {replicas} replicas",
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
service_info = None
|
|
951
|
+
load_balancer_ip = None
|
|
952
|
+
|
|
953
|
+
# Create LoadBalancer service if requested and ports are specified
|
|
954
|
+
if create_service and ports:
|
|
955
|
+
parsed_ports = []
|
|
956
|
+
for port_spec in ports:
|
|
957
|
+
port_info = self._parse_port_spec(port_spec)
|
|
958
|
+
if port_info:
|
|
959
|
+
parsed_ports.append(port_info)
|
|
960
|
+
|
|
961
|
+
if parsed_ports:
|
|
962
|
+
(
|
|
963
|
+
service_created,
|
|
964
|
+
service_name,
|
|
965
|
+
) = self._create_loadbalancer_service(
|
|
966
|
+
name,
|
|
967
|
+
parsed_ports,
|
|
968
|
+
)
|
|
969
|
+
if service_created:
|
|
970
|
+
service_info = {
|
|
971
|
+
"name": service_name,
|
|
972
|
+
"type": "LoadBalancer",
|
|
973
|
+
"ports": [p["port"] for p in parsed_ports],
|
|
974
|
+
}
|
|
975
|
+
# Wait a bit and try to get LoadBalancer IP
|
|
976
|
+
time.sleep(2)
|
|
977
|
+
load_balancer_ip = self._get_loadbalancer_ip(
|
|
978
|
+
service_name,
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
# Wait for deployment to be ready
|
|
982
|
+
if not self.wait_for_deployment_ready(name, timeout=120):
|
|
983
|
+
logger.warning(f"Deployment '{name}' may not be fully ready")
|
|
984
|
+
|
|
985
|
+
logger.debug(
|
|
986
|
+
f"Deployment '{name}' created successfully with service: "
|
|
987
|
+
f"{service_info}",
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
return (
|
|
991
|
+
name,
|
|
992
|
+
service_info["ports"] if service_info else [],
|
|
993
|
+
load_balancer_ip or "",
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
except Exception as e:
|
|
997
|
+
logger.error(
|
|
998
|
+
f"Failed to create deployment: {e}, {traceback.format_exc()}",
|
|
999
|
+
)
|
|
1000
|
+
return None, None, None
|
|
1001
|
+
|
|
1002
|
+
def wait_for_deployment_ready(self, deployment_name, timeout=300):
|
|
1003
|
+
"""Wait for a deployment to be ready."""
|
|
1004
|
+
start_time = time.time()
|
|
1005
|
+
while time.time() - start_time < timeout:
|
|
1006
|
+
try:
|
|
1007
|
+
deployment = self.apps_v1.read_namespaced_deployment(
|
|
1008
|
+
name=deployment_name,
|
|
1009
|
+
namespace=self.namespace,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
# Check if deployment is ready
|
|
1013
|
+
if (
|
|
1014
|
+
deployment.status.ready_replicas
|
|
1015
|
+
and deployment.status.ready_replicas
|
|
1016
|
+
== deployment.spec.replicas
|
|
1017
|
+
):
|
|
1018
|
+
return True
|
|
1019
|
+
|
|
1020
|
+
time.sleep(3)
|
|
1021
|
+
except ApiException as e:
|
|
1022
|
+
if e.status == 404:
|
|
1023
|
+
return False
|
|
1024
|
+
time.sleep(3)
|
|
1025
|
+
return False
|
|
1026
|
+
|
|
1027
|
+
def _get_loadbalancer_ip(self, service_name, timeout=30):
|
|
1028
|
+
"""Get LoadBalancer external IP address."""
|
|
1029
|
+
start_time = time.time()
|
|
1030
|
+
while time.time() - start_time < timeout:
|
|
1031
|
+
try:
|
|
1032
|
+
service = self.v1.read_namespaced_service(
|
|
1033
|
+
name=service_name,
|
|
1034
|
+
namespace=self.namespace,
|
|
1035
|
+
)
|
|
1036
|
+
|
|
1037
|
+
if (
|
|
1038
|
+
service.status.load_balancer
|
|
1039
|
+
and service.status.load_balancer.ingress
|
|
1040
|
+
):
|
|
1041
|
+
ingress = service.status.load_balancer.ingress[0]
|
|
1042
|
+
# Return IP or hostname
|
|
1043
|
+
return ingress.ip or ingress.hostname
|
|
1044
|
+
|
|
1045
|
+
time.sleep(2)
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
logger.debug(f"Waiting for LoadBalancer IP: {e}")
|
|
1048
|
+
time.sleep(2)
|
|
1049
|
+
|
|
1050
|
+
logger.debug(
|
|
1051
|
+
f"LoadBalancer IP not available for service {service_name}",
|
|
1052
|
+
)
|
|
1053
|
+
return None
|
|
1054
|
+
|
|
1055
|
+
def remove_deployment(self, deployment_name, remove_service=True):
|
|
1056
|
+
"""Remove a Kubernetes Deployment and optionally its service."""
|
|
1057
|
+
try:
|
|
1058
|
+
# Remove associated LoadBalancer service first if requested
|
|
1059
|
+
if remove_service:
|
|
1060
|
+
service_name = f"{deployment_name}-lb-service"
|
|
1061
|
+
try:
|
|
1062
|
+
self.v1.delete_namespaced_service(
|
|
1063
|
+
name=service_name,
|
|
1064
|
+
namespace=self.namespace,
|
|
1065
|
+
)
|
|
1066
|
+
logger.debug(
|
|
1067
|
+
f"Removed LoadBalancer service {service_name}",
|
|
1068
|
+
)
|
|
1069
|
+
except ApiException as e:
|
|
1070
|
+
if e.status != 404:
|
|
1071
|
+
logger.warning(
|
|
1072
|
+
f"Failed to remove service {service_name}: {e}",
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
# Remove the deployment
|
|
1076
|
+
self.apps_v1.delete_namespaced_deployment(
|
|
1077
|
+
name=deployment_name,
|
|
1078
|
+
namespace=self.namespace,
|
|
1079
|
+
body=client.V1DeleteOptions(
|
|
1080
|
+
propagation_policy="Foreground",
|
|
1081
|
+
),
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
logger.debug(
|
|
1085
|
+
f"Deployment '{deployment_name}' removed successfully",
|
|
1086
|
+
)
|
|
1087
|
+
return True
|
|
1088
|
+
|
|
1089
|
+
except ApiException as e:
|
|
1090
|
+
if e.status == 404:
|
|
1091
|
+
logger.warning(f"Deployment '{deployment_name}' not found")
|
|
1092
|
+
return True
|
|
1093
|
+
logger.error(f"Failed to remove deployment: {e.reason}")
|
|
1094
|
+
return False
|
|
1095
|
+
except Exception as e:
|
|
1096
|
+
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
|
|
1097
|
+
return False
|
|
1098
|
+
|
|
1099
|
+
def get_deployment_status(self, deployment_name):
|
|
1100
|
+
"""Get the current status of the specified deployment."""
|
|
1101
|
+
try:
|
|
1102
|
+
deployment = self.apps_v1.read_namespaced_deployment(
|
|
1103
|
+
name=deployment_name,
|
|
1104
|
+
namespace=self.namespace,
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
return {
|
|
1108
|
+
"name": deployment_name,
|
|
1109
|
+
"replicas": deployment.spec.replicas,
|
|
1110
|
+
"ready_replicas": deployment.status.ready_replicas or 0,
|
|
1111
|
+
"available_replicas": deployment.status.available_replicas
|
|
1112
|
+
or 0,
|
|
1113
|
+
"unavailable_replicas": deployment.status.unavailable_replicas
|
|
1114
|
+
or 0,
|
|
1115
|
+
"conditions": [
|
|
1116
|
+
{
|
|
1117
|
+
"type": condition.type,
|
|
1118
|
+
"status": condition.status,
|
|
1119
|
+
"reason": condition.reason,
|
|
1120
|
+
"message": condition.message,
|
|
1121
|
+
}
|
|
1122
|
+
for condition in (deployment.status.conditions or [])
|
|
1123
|
+
],
|
|
1124
|
+
}
|
|
1125
|
+
except ApiException as e:
|
|
1126
|
+
if e.status == 404:
|
|
1127
|
+
logger.warning(f"Deployment '{deployment_name}' not found")
|
|
1128
|
+
else:
|
|
1129
|
+
logger.error(f"Failed to get deployment status: {e.reason}")
|
|
1130
|
+
return None
|
|
1131
|
+
except Exception as e:
|
|
1132
|
+
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
|
|
1133
|
+
return None
|
|
1134
|
+
|
|
1135
|
+
def list_deployments(self, label_selector=None):
|
|
1136
|
+
"""List deployments in the namespace."""
|
|
1137
|
+
try:
|
|
1138
|
+
deployments = self.apps_v1.list_namespaced_deployment(
|
|
1139
|
+
namespace=self.namespace,
|
|
1140
|
+
label_selector=label_selector,
|
|
1141
|
+
)
|
|
1142
|
+
return [
|
|
1143
|
+
deployment.metadata.name for deployment in deployments.items
|
|
1144
|
+
]
|
|
1145
|
+
except ApiException as e:
|
|
1146
|
+
logger.error(f"Failed to list deployments: {e.reason}")
|
|
1147
|
+
return []
|
|
1148
|
+
except Exception as e:
|
|
1149
|
+
logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
|
|
1150
|
+
return []
|
agentscope_runtime/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "0.1.5b1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentscope-runtime
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5b1
|
|
4
4
|
Summary: A production-ready runtime framework for agent applications, providing secure sandboxed execution environments and scalable deployment solutions with multi-framework support.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -8,7 +8,7 @@ License-File: LICENSE
|
|
|
8
8
|
Requires-Dist: mcp>=1.13
|
|
9
9
|
Requires-Dist: fastapi>=0.104.0
|
|
10
10
|
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
11
|
-
Requires-Dist: openai
|
|
11
|
+
Requires-Dist: openai>=1.108.1
|
|
12
12
|
Requires-Dist: pydantic>=2.11.7
|
|
13
13
|
Requires-Dist: requests>=2.32.4
|
|
14
14
|
Provides-Extra: dev
|
|
@@ -56,6 +56,22 @@ Requires-Dist: langchain-community>=0.3.27; extra == "llamaindex-rag"
|
|
|
56
56
|
Requires-Dist: bs4>=0.0.2; extra == "llamaindex-rag"
|
|
57
57
|
Provides-Extra: memory-ext
|
|
58
58
|
Requires-Dist: reme-ai==0.1.9; python_full_version >= "3.12" and extra == "memory-ext"
|
|
59
|
+
Requires-Dist: mem0ai>=0.1.117; extra == "memory-ext"
|
|
60
|
+
Provides-Extra: deployment
|
|
61
|
+
Requires-Dist: alibabacloud-oss-v2; extra == "deployment"
|
|
62
|
+
Requires-Dist: alibabacloud-bailian20231229>=2.5.0; extra == "deployment"
|
|
63
|
+
Requires-Dist: build; extra == "deployment"
|
|
64
|
+
Requires-Dist: setuptools>=40.8.0; extra == "deployment"
|
|
65
|
+
Requires-Dist: wheel; extra == "deployment"
|
|
66
|
+
Requires-Dist: steel-sdk>=0.1.0; extra == "deployment"
|
|
67
|
+
Requires-Dist: alibabacloud-credentials; extra == "deployment"
|
|
68
|
+
Requires-Dist: jinja2; extra == "deployment"
|
|
69
|
+
Requires-Dist: psutil; extra == "deployment"
|
|
70
|
+
Requires-Dist: shortuuid>=1.0.13; extra == "deployment"
|
|
71
|
+
Requires-Dist: docker>=7.1.0; extra == "deployment"
|
|
72
|
+
Requires-Dist: kubernetes>=33.1.0; extra == "deployment"
|
|
73
|
+
Requires-Dist: PyYAML; extra == "deployment"
|
|
74
|
+
Requires-Dist: oss2>=2.19.1; extra == "deployment"
|
|
59
75
|
Dynamic: license-file
|
|
60
76
|
|
|
61
77
|
<div align="center">
|
|
@@ -405,7 +421,7 @@ limitations under the License.
|
|
|
405
421
|
|
|
406
422
|
## Contributors ✨
|
|
407
423
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
408
|
-
[](#contributors-)
|
|
409
425
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
410
426
|
|
|
411
427
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
@@ -425,7 +441,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|
|
425
441
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jinliyl"><img src="https://avatars.githubusercontent.com/u/6469360?v=4?s=100" width="100px;" alt="jinliyl"/><br /><sub><b>jinliyl</b></sub></a><br /><a href="https://github.com/agentscope-ai/agentscope-runtime/commits?author=jinliyl" title="Code">💻</a> <a href="https://github.com/agentscope-ai/agentscope-runtime/commits?author=jinliyl" title="Documentation">📖</a></td>
|
|
426
442
|
</tr>
|
|
427
443
|
<tr>
|
|
428
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Osier-Yi"><img src="https://avatars.githubusercontent.com/u/8287381?v=4?s=100" width="100px;" alt="Osier-Yi"/><br /><sub><b>Osier-Yi</b></sub></a><br /><a href="https://github.com/agentscope-ai/agentscope-runtime/commits?author=Osier-Yi" title="Code">💻</a></td>
|
|
444
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Osier-Yi"><img src="https://avatars.githubusercontent.com/u/8287381?v=4?s=100" width="100px;" alt="Osier-Yi"/><br /><sub><b>Osier-Yi</b></sub></a><br /><a href="https://github.com/agentscope-ai/agentscope-runtime/commits?author=Osier-Yi" title="Code">💻</a> <a href="https://github.com/agentscope-ai/agentscope-runtime/commits?author=Osier-Yi" title="Documentation">📖</a></td>
|
|
445
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kevinlin09"><img src="https://avatars.githubusercontent.com/u/26913335?v=4?s=100" width="100px;" alt="Kevin Lin"/><br /><sub><b>Kevin Lin</b></sub></a><br /><a href="https://github.com/agentscope-ai/agentscope-runtime/commits?author=kevinlin09" title="Code">💻</a></td>
|
|
429
446
|
</tr>
|
|
430
447
|
</tbody>
|
|
431
448
|
<tfoot>
|